1883 lines
81 KiB
PHP
1883 lines
81 KiB
PHP
<?php
|
|
# Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team)
|
|
# All rights reserved. See LICENSE file for licensing details
|
|
|
|
use LuckyCoin\ContentCache;
|
|
|
|
if (IN_serendipity !== true) {
|
|
die ("Don't hack!");
|
|
}
|
|
|
|
/**
|
|
* Delete a category or range of categories
|
|
*
|
|
* @access public
|
|
* @param string Holds the SQL string to pass to the 'BETWEEN' command. Like '1 5' would delete categories 1-5.
|
|
* @param string Holds the optional SQL string that contains an extra safety check so that only categories can be deleted if the user is an author of the category.
|
|
* @return array The DB result
|
|
*/
|
|
function serendipity_deleteCategory($category_range, $admin_category) {
|
|
global $serendipity;
|
|
|
|
if (!serendipity_checkPermission('adminCategoriesDelete')) {
|
|
return false;
|
|
}
|
|
|
|
serendipity_plugin_api::hook_event('backend_category_delete', $category_range);
|
|
|
|
return serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}category WHERE category_left BETWEEN {$category_range} {$admin_category}");
|
|
}
|
|
|
|
/**
|
|
* Fetch a SQL ID subset of the category tree
|
|
*
|
|
* @access public
|
|
* @param int The Id of the parent category to fetch categorie childs from. (0: all)
|
|
* @return array An associative array of the left and right category next to the chosen one
|
|
*/
|
|
function serendipity_fetchCategoryRange($categoryid) {
|
|
global $serendipity;
|
|
|
|
$res =& serendipity_db_query("SELECT category_left, category_right, hide_sub FROM {$serendipity['dbPrefix']}category WHERE categoryid='". (int)$categoryid ."'");
|
|
if (!is_array($res) || !isset($res[0]['category_left']) || !isset($res[0]['category_right'])) {
|
|
$res = array(array('category_left' => 0, 'category_right' => 0));
|
|
}
|
|
|
|
if (($res[0]['hide_sub'] ?? null) == 1) {
|
|
// Set ranges only to own category. Patch by netmorix
|
|
return array('category_left' => $res[0]['category_left'], 'category_right' => $res[0]['category_left']);
|
|
} else {
|
|
return array('category_left' => $res[0]['category_left'], 'category_right' => $res[0]['category_right']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns SQL code to use when fetching entries that are contained within multiple categories
|
|
*
|
|
* @access public
|
|
* @param string A listing of category ids to check, separated by ";"
|
|
* @param boolean Toggle whether to include or exclude entries of this category
|
|
* @return string Returns the SQL code for selecting entries of the calculated categories
|
|
*/
|
|
function serendipity_getMultiCategoriesSQL($cats, $invert = false) {
|
|
global $serendipity;
|
|
|
|
$mcategories = explode(';', $cats);
|
|
$cat_sql_array = array();
|
|
foreach($mcategories AS $categoryid) {
|
|
$categoryid = (int)$categoryid;
|
|
|
|
if ($categoryid != 0) {
|
|
$cat_sql_array[] = " (c.category_left " . ($invert ? " NOT " : "") . " BETWEEN " . implode(' AND ', serendipity_fetchCategoryRange($categoryid)) . ')';
|
|
}
|
|
}
|
|
|
|
if (count($cat_sql_array) < 1) {
|
|
return '';
|
|
}
|
|
|
|
return '(' . implode(($invert ? ' AND ' : ' OR '), $cat_sql_array) . ')';
|
|
}
|
|
|
|
/**
|
|
* Return the category properties of a specific category
|
|
*
|
|
* Either use the first or the second parameter to select a category by ID or name. It's not
|
|
* meant to be used with both parameters specified.
|
|
*
|
|
* @access public
|
|
* @param int The ID of the category to fetch
|
|
* @param string The Name of a category to fetch
|
|
* @return array Returns an array of category properties
|
|
*/
|
|
function serendipity_fetchCategoryInfo($categoryid, $categoryname = '') {
|
|
global $serendipity;
|
|
|
|
if (!empty($categoryname)) {
|
|
$query = "SELECT
|
|
c.authorid,
|
|
c.categoryid,
|
|
c.category_name,
|
|
c.category_description,
|
|
c.category_icon,
|
|
c.parentid,
|
|
c.hide_sub
|
|
FROM {$serendipity['dbPrefix']}category AS c
|
|
WHERE category_name = '" . serendipity_db_escape_string($categoryname) . "'";
|
|
|
|
$ret =& serendipity_db_query($query);
|
|
return $ret[0];
|
|
} else {
|
|
$query = "SELECT
|
|
c.authorid,
|
|
c.categoryid,
|
|
c.category_name,
|
|
c.category_description,
|
|
c.category_icon,
|
|
c.parentid,
|
|
c.hide_sub
|
|
FROM {$serendipity['dbPrefix']}category AS c
|
|
WHERE categoryid = " . (int)$categoryid;
|
|
|
|
$ret =& serendipity_db_query($query);
|
|
return $ret[0];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch a list of all category properties to a specific entry ID
|
|
*
|
|
* @access public
|
|
* @param int The ID of the entry
|
|
* @return array The array of associated categories to that entry
|
|
*/
|
|
function &serendipity_fetchEntryCategories($entryid) {
|
|
global $serendipity;
|
|
|
|
if (is_numeric($entryid)) {
|
|
$query = "SELECT
|
|
c.categoryid,
|
|
c.category_name,
|
|
c.category_description,
|
|
c.category_icon,
|
|
c.parentid
|
|
FROM {$serendipity['dbPrefix']}category AS c
|
|
INNER JOIN {$serendipity['dbPrefix']}entrycat AS ec
|
|
ON ec.categoryid = c.categoryid
|
|
WHERE ec.entryid = {$entryid}
|
|
ORDER BY c.category_name ASC";
|
|
|
|
$cat =& serendipity_db_query($query,false,'assoc');
|
|
|
|
if (!is_array($cat)) {
|
|
$arr = array();
|
|
return $arr;
|
|
} else {
|
|
serendipity_plugin_api::hook_event('multilingual_strip_langs',$cat, array('category_name','category_description'));
|
|
return $cat;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetch a list of entries
|
|
*
|
|
* The most central and versatile function of Serendipity, allows you to fetch entries
|
|
* depending on a LOT of options.
|
|
* One of the parameters missing is a restriction by category. You need to do that by
|
|
* setting the superglobal $serendipity['GET']['category'] to the category you want to fetch.
|
|
* Separate multiple categories by ";".
|
|
* Other "external" variables that affect this function are:
|
|
* $serendipity['short_archives'] - Indicates if the short archive listing is wanted, without the full entry text
|
|
* $serendipity['range'] - If $range is not used, the time restriction is fetched from this array, which holds a start timestamp and end timestamp.
|
|
* $serendipity['GET']['category'] - The category ID to restrict fetching entries from (can be separated by ";")
|
|
* $serendipity['GET']['hide_category']- The category ID to NOT fetch entries from (can be separated by ";")
|
|
* $serendipity['GET']['viewAuthor'] - Only fetch entries by this author
|
|
* $serendipity['GET']['page'] - The page number to show entries, for pagination
|
|
*
|
|
* If you want to use any of these options, set the variable before calling serendipity_fetchEntries(). You can reset the variables to their original content after the function call, if you need to.
|
|
*
|
|
* Several options perform different commands when different types are passed, like the $range
|
|
* parameter which can either be a string or an array with START/END range.
|
|
*
|
|
* @access public
|
|
* @param mixed Restricts fetching entries to a specific timespan. Behaves differently depending on the type:
|
|
* Numeric:
|
|
* YYYYMMDD - Shows all entries from YYYY-MM-DD.
|
|
* If DD is "00", it will show all entries from that month.
|
|
* If DD is any other number, it will show entries of that specific day.
|
|
* 2-Dimensional Array:
|
|
* Key #0 - Specifies the start timestamp (unix seconds)
|
|
* Key #1 - Specifies the end timestamp (unix seconds)
|
|
* Other (null, 3-dimensional Array, ...):
|
|
* Entries newer than $modified_since will be fetched
|
|
* @param boolean Indicates if the full entry will be fetched (body+extended: TRUE), or only the body (FALSE).
|
|
* @param string Holds a "Y" or "X, Y" string that tells which entries to fetch. X is the first entry offset, Y is number of entries. If not set, the global fetchLimit will be applied (15 entries by default)
|
|
* @param boolean Indicates whether drafts should be fetched (TRUE) or not
|
|
* @param int Holds a unix timestamp to be used in conjunction with $range, to fetch all entries newer than this timestamp
|
|
* @param string Holds the SQL "ORDER BY" statement.
|
|
* @param string Can contain any SQL code to inject into the central SQL statement for fetching the entry
|
|
* @param boolean If set to TRUE, all entries will be fetched from scratch and any caching is ignored
|
|
* @param boolean If set to TRUE, all sticky entries will NOT be fetched.
|
|
* @param string Can contain a SQL statement on which keys to select. Plugins can also set this, pay attention!
|
|
* @param string Can contain a SQL statement on how to group the query. Plugins can also set this, pay attention!
|
|
* @param string If set to "array", the array of entries will be returned. "flat-array" will only return the articles without their entryproperties. "single" will only return a 1-dimensional array. "query" will only return the used SQL.
|
|
* @param bool Should an SQL-join be made to the AUTHORS DB table?
|
|
* @param bool Should an SQL-join be made to the CATEGORIES DB table?
|
|
* @param string SQL-Parts to add to JOIN
|
|
* @return array Holds the super-array of all entries with all additional information
|
|
*/
|
|
function &serendipity_fetchEntries($range = null, $full = true, $limit = '', $fetchDrafts = false, $modified_since = false, $orderby = 'timestamp DESC', $filter_sql = '', $noCache = false, $noSticky = false, $select_key = null, $group_by = null, $returncode = 'array', $joinauthors = true, $joincategories = true, $joinown = null) {
|
|
global $serendipity;
|
|
|
|
$initial_args = array_values(func_get_args());
|
|
|
|
if ($serendipity['useInternalCache']) {
|
|
$key = md5(serialize($initial_args) . ($serendipity['short_archives'] ?? '') . '||' . (serialize($serendipity['range'] ?? '')) . '||' . ($serendipity['GET']['category'] ?? '') . '||' . ($serendipity['GET']['hide_category'] ?? '') . '||' . ($serendipity['GET']['viewAuthor'] ?? '') . '||' . ($serendipity['GET']['page'] ?? '') . '||' . $serendipity['fetchLimit'] . '||' . $serendipity['max_fetch_limit'] . '||' . ($serendipity['GET']['adminModule'] ?? '') . '||' . serendipity_checkPermission('adminEntriesMaintainOthers') . '||' .$serendipity['showFutureEntries'] . '||' . $serendipity['archiveSortStable'] . '||' . ($serendipity['plugindata']['smartyvars']['uriargs'] ?? '') );
|
|
|
|
$cache = ContentCache::getInstance();
|
|
$entries = $cache->getItem($key);
|
|
if ($entries && $entries !== false) {
|
|
$serendipity['fullCountQuery'] = $cache->getItem($key . '_fullCountQuery');
|
|
return unserialize($entries);
|
|
}
|
|
}
|
|
|
|
$cond = array();
|
|
$cond['orderby'] = $orderby;
|
|
if (isset($serendipity['short_archives']) && $serendipity['short_archives']) {
|
|
// In the short listing of all titles for a month, we don't want to have a limit applied. And we don't need/want the
|
|
// full article body (consumes memory)
|
|
$limit = '';
|
|
$full = false;
|
|
}
|
|
|
|
if ($full === true) {
|
|
$noCache = true; // So no entryproperties related to body/extended caching will be loaded
|
|
$body = ', e.body, e.extended';
|
|
} else {
|
|
$body = '';
|
|
}
|
|
|
|
if ($fetchDrafts === false) {
|
|
$drafts = "isdraft = 'false'";
|
|
}
|
|
|
|
if ($limit != '') {
|
|
$serendipity['fetchLimit'] = $limit;
|
|
}
|
|
|
|
/* Attempt to grab range from $serendipity, if $range is not an array or null */
|
|
if (!is_array($range) && !is_null($range) && isset($serendipity['range'])) {
|
|
$range = $serendipity['range'];
|
|
}
|
|
|
|
if (is_numeric($range)) {
|
|
$year = (int)substr($range, 0, 4);
|
|
$month = (int)substr($range, 4, 2);
|
|
$day = (int)substr($range, 6, 2);
|
|
|
|
$startts = serendipity_serverOffsetHour(mktime(0, 0, 0, $month, ($day == 0 ? 1 : $day), $year), true);
|
|
|
|
if ($day == 0) {
|
|
$month++;
|
|
} else {
|
|
$day++;
|
|
}
|
|
|
|
$endts = serendipity_serverOffsetHour(mktime(0, 0, 0, $month, ($day == 0 ? 1 : $day), $year), true);
|
|
|
|
$cond['and'] = " WHERE e.timestamp >= $startts AND e.timestamp <= $endts";
|
|
} elseif (is_array($range) && count($range)==2) {
|
|
$startts = serendipity_serverOffsetHour((int)$range[0], true);
|
|
$endts = serendipity_serverOffsetHour((int)$range[1], true);
|
|
$cond['and'] = " WHERE e.timestamp >= $startts AND e.timestamp <= $endts";
|
|
} else {
|
|
if ($modified_since) {
|
|
$unix_modified = strtotime($modified_since);
|
|
if ($unix_modified != -1) {
|
|
$cond['and'] = ' WHERE last_modified >= ' . (int)$unix_modified;
|
|
if (!empty($limit)) {
|
|
$limit = ($limit > $serendipity['max_fetch_limit'] ? $limit : $serendipity['max_fetch_limit']);
|
|
}
|
|
$cond['orderby'] = 'last_modified DESC';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($drafts)) {
|
|
if (!empty($cond['and'])) {
|
|
$cond['and'] .= " AND $drafts";
|
|
} else {
|
|
$cond['and'] = "WHERE $drafts";
|
|
}
|
|
}
|
|
|
|
if (isset($serendipity['GET']['viewAuthor'])) {
|
|
$multiauthors = explode(';', $serendipity['GET']['viewAuthor']);
|
|
$multiauthors_sql = array();
|
|
foreach($multiauthors AS $multiauthor) {
|
|
$multiauthors_sql[] = 'e.authorid = ' . (int)$multiauthor;
|
|
}
|
|
|
|
$cond['and'] .= ' AND (' . implode(' OR ', $multiauthors_sql) . ')';
|
|
}
|
|
|
|
$cat_sql = '';
|
|
if (isset($serendipity['GET']['category'])) {
|
|
$cat_sql = serendipity_getMultiCategoriesSQL($serendipity['GET']['category']);
|
|
} elseif (isset($serendipity['GET']['hide_category'])) {
|
|
$cat_sql = serendipity_getMultiCategoriesSQL($serendipity['GET']['hide_category'], true);
|
|
}
|
|
|
|
if (!empty($cat_sql)) {
|
|
if (!empty($cond['and'])) {
|
|
$cond['and'] .= " AND ($cat_sql)";
|
|
} else {
|
|
$cond['and'] = "WHERE ($cat_sql)";
|
|
}
|
|
}
|
|
|
|
if (isset($serendipity['GET']['adminModule']) && $serendipity['GET']['adminModule'] == 'entries' && !serendipity_checkPermission('adminEntriesMaintainOthers')) {
|
|
if (!empty($cond['and'])) {
|
|
$cond['and'] .= " AND e.authorid = '" . $serendipity['authorid'] . "'";
|
|
} else {
|
|
$cond['and'] = "WHERE e.authorid = '" . $serendipity['authorid'] . "'";
|
|
}
|
|
}
|
|
|
|
if (!isset($serendipity['GET']['adminModule']) && !serendipity_db_bool($serendipity['showFutureEntries'])) {
|
|
if (!empty($cond['and'])) {
|
|
$cond['and'] .= " AND e.timestamp <= " . serendipity_db_time();
|
|
} else {
|
|
$cond['and'] = "WHERE e.timestamp <= " . serendipity_db_time();
|
|
}
|
|
}
|
|
|
|
if (!empty($filter_sql)) {
|
|
if (!empty($cond['and'])) {
|
|
$cond['and'] .= ' AND ' . $filter_sql;
|
|
} else {
|
|
$cond['and'] = 'WHERE ' . $filter_sql;
|
|
}
|
|
}
|
|
|
|
if ($serendipity['dbType'] == 'postgres' ||
|
|
$serendipity['dbType'] == 'pdo-postgres') {
|
|
$cond['group'] = '';
|
|
$cond['distinct'] = 'DISTINCT';
|
|
} else {
|
|
$cond['group'] = 'GROUP BY e.id';
|
|
$cond['distinct'] = '';
|
|
}
|
|
|
|
if (!is_null($group_by)) {
|
|
$cond['group'] = $group_by;
|
|
}
|
|
|
|
serendipity_plugin_api::hook_event('frontend_fetchentries', $cond, array('noCache' => $noCache, 'noSticky' => $noSticky, 'source' => 'entries'));
|
|
if (!isset($cond['addkey'])) {
|
|
$cond['addkey'] = '';
|
|
}
|
|
|
|
if (is_null($select_key)) {
|
|
$select_key = "{$cond['distinct']}
|
|
{$cond['addkey']}
|
|
e.id,
|
|
e.title,
|
|
e.timestamp,
|
|
e.comments,
|
|
e.exflag,
|
|
e.authorid,
|
|
e.trackbacks,
|
|
e.isdraft,
|
|
e.allow_comments,
|
|
e.last_modified,
|
|
a.realname AS author,
|
|
a.username AS loginname,
|
|
a.email";
|
|
}
|
|
|
|
serendipity_ACL_SQL($cond);
|
|
|
|
// Store the unique query condition for entries for later reference, like getting the total article count.
|
|
$serendipity['fullCountQuery'] = "
|
|
FROM
|
|
{$serendipity['dbPrefix']}entries AS e";
|
|
if ($joinauthors) {
|
|
$serendipity['fullCountQuery'] .= "
|
|
LEFT JOIN {$serendipity['dbPrefix']}authors a
|
|
ON e.authorid = a.authorid";
|
|
}
|
|
|
|
if ($joincategories || !isset($serendipity['enableACL']) || $serendipity['enableACL'] == true) {
|
|
// Category joins are REQUIRED when the ACLs are enabled.
|
|
$serendipity['fullCountQuery'] .= "
|
|
LEFT JOIN {$serendipity['dbPrefix']}entrycat ec
|
|
ON e.id = ec.entryid
|
|
LEFT JOIN {$serendipity['dbPrefix']}category c
|
|
ON ec.categoryid = c.categoryid";
|
|
}
|
|
|
|
if (!isset($cond['joins'])) {
|
|
$cond['joins'] = '';
|
|
}
|
|
if (!isset($cond['and'])) {
|
|
$cond['and'] = '';
|
|
}
|
|
if ($joinown) {
|
|
$cond['joins'] .= $joinown;
|
|
}
|
|
|
|
$serendipity['fullCountQuery'] .="
|
|
{$cond['joins']}
|
|
{$cond['and']}";
|
|
|
|
if (!empty($limit)) {
|
|
if (isset($serendipity['GET']['page']) && ($serendipity['GET']['page'] > 1 || serendipity_db_bool($serendipity['archiveSortStable'])) && !strstr($limit, ',')) {
|
|
|
|
// serendipity_fetchEntries() is mostly called in page context to
|
|
// deliver a list of entries to display on archive, search result
|
|
// or other pages, where $limit means the number of entries to
|
|
// display on each page. This code depends on that notion,
|
|
// catches request for non-existing pages and applies
|
|
// pagination.
|
|
// If you don't work in a page context, e.g. you just want to fetch
|
|
// a list of entries to display in a sidebar, you should think
|
|
// about saving and unsetting $serendipity['GET']['page'] before
|
|
// calling serendipity_fetchEntries() and reset it to the saved
|
|
// value afterwards.
|
|
// See https://github.com/s9y/Serendipity/issues/693 for context.
|
|
|
|
$totalEntries = serendipity_getTotalEntries();
|
|
$totalPages = ceil($totalEntries / $limit);
|
|
|
|
// Do not allow requesting a page that doesn't exist
|
|
// and do a fallback to the highest page number available
|
|
if ($totalPages > 0 && $serendipity['GET']['page'] > $totalPages) {
|
|
$serendipity['GET']['page'] = $totalPages;
|
|
}
|
|
|
|
if (serendipity_db_bool($serendipity['archiveSortStable'])) {
|
|
|
|
if ($totalPages <= 0 ) {
|
|
$totalPages = 1;
|
|
}
|
|
|
|
if ($serendipity['GET']['page'] == $totalPages) {
|
|
$limit = serendipity_db_limit(0, $limit);
|
|
} else if ($serendipity['GET']['page'] == $totalPages - 1) {
|
|
$limit = serendipity_db_limit($limit, ($totalEntries - (($totalPages -2) * $limit)) - $limit);
|
|
} else {
|
|
$limit = serendipity_db_limit(max(0, ($totalEntries - ($limit * $serendipity['GET']['page']))), $limit);
|
|
}
|
|
} else {
|
|
$limit = serendipity_db_limit(($serendipity['GET']['page']-1) * $limit, $limit);
|
|
}
|
|
}
|
|
|
|
$limit = serendipity_db_limit_sql($limit);
|
|
|
|
} else {
|
|
# if $limit was empty(), no limit was set;
|
|
# but empty() may also mean FALSE, "" or 0,
|
|
# so set it to "" to remove the LIMIT
|
|
# statement completely; should catch errors
|
|
# with a limit of 0 and some database drivers
|
|
# see <https://github.com/s9y/Serendipity/issues/636>
|
|
$limit = '';
|
|
}
|
|
|
|
if (! array_key_exists('having', $cond)) {
|
|
$cond['having'] = '';
|
|
}
|
|
$query = "SELECT $select_key
|
|
$body
|
|
{$serendipity['fullCountQuery']}
|
|
{$cond['group']}
|
|
{$cond['having']}
|
|
" . (!empty($cond['orderby']) ? "ORDER BY {$cond['orderby']}" : "") . "
|
|
$limit";
|
|
|
|
// DEBUG:
|
|
// die($query);
|
|
$fetch_single = ($returncode == 'single' ? true: false);
|
|
|
|
if ($returncode == 'query') {
|
|
return $query;
|
|
}
|
|
|
|
$ret =& serendipity_db_query($query, $fetch_single, 'assoc');
|
|
|
|
if (is_string($ret)) {
|
|
die("Query failed: $ret");
|
|
}
|
|
|
|
if (is_array($ret) && $returncode == 'array') {
|
|
// The article's query LIMIT operates on a flattened entries layer so that
|
|
// an article having 5 associated categories won't count as 5 entries.
|
|
// But to store the expanded list of categories, we need to send a new
|
|
// query once for all entries we have just fetched.
|
|
// First code for this was sending 15 queries for 15 fetched entries,
|
|
// this is now limited to just one query per fetched articles group
|
|
|
|
serendipity_fetchEntryData($ret);
|
|
}
|
|
|
|
if ($serendipity['useInternalCache']) {
|
|
$key = md5(serialize($initial_args) . ($serendipity['short_archives'] ?? '') . '||' . (serialize($serendipity['range'] ?? '')) . '||' . ($serendipity['GET']['category'] ?? '') . '||' . ($serendipity['GET']['hide_category'] ?? '') . '||' . ($serendipity['GET']['viewAuthor'] ?? '') . '||' . ($serendipity['GET']['page'] ?? '') . '||' . $serendipity['fetchLimit'] . '||' . $serendipity['max_fetch_limit'] . '||' . ($serendipity['GET']['adminModule'] ?? '') . '||' . serendipity_checkPermission('adminEntriesMaintainOthers') . '||' .$serendipity['showFutureEntries'] . '||' . $serendipity['archiveSortStable'] . '||' . ($serendipity['plugindata']['smartyvars']['uriargs'] ?? '') );
|
|
|
|
$cache = ContentCache::getInstance();
|
|
$cache->addItem($key, serialize($ret));
|
|
$cache->addItem($key . '_fullCountQuery', $serendipity['fullCountQuery']);
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Fetch special entry data and attach it to a superarray of entries.
|
|
*
|
|
* Fetches all additional information like plugins, extended properties, additional categories for each entry.
|
|
*
|
|
* @access private
|
|
* @see serendipity_fetchEntries()
|
|
* @param array The array of entries where the output will be merged to (referenced)
|
|
* @return null
|
|
*/
|
|
function serendipity_fetchEntryData(&$ret) {
|
|
global $serendipity;
|
|
|
|
$search_ids = array(); // An array to hold all ids of the entry we want to fetch.
|
|
$assoc_ids = array(); // A temporary key association container to not have to loop through the return array once again.
|
|
|
|
foreach($ret AS $i => $entry) {
|
|
$search_ids[] = $entry['id'];
|
|
$ret[$i]['categories'] = array(); // make sure every article gets its category association
|
|
$assoc_ids[$entry['id']] = $i; // store temporary reference
|
|
}
|
|
|
|
serendipity_plugin_api::hook_event('frontend_entryproperties', $ret, $assoc_ids);
|
|
|
|
$query = "SELECT
|
|
ec.entryid,
|
|
c.categoryid,
|
|
c.category_name,
|
|
c.category_description,
|
|
c.category_icon,
|
|
c.parentid
|
|
FROM {$serendipity['dbPrefix']}category AS c
|
|
LEFT JOIN {$serendipity['dbPrefix']}entrycat AS ec
|
|
ON ec.categoryid = c.categoryid
|
|
WHERE " . serendipity_db_in_sql('ec.entryid', $search_ids) . "
|
|
ORDER BY c.category_name ASC";
|
|
|
|
$search_ret =& serendipity_db_query($query, false, 'assoc');
|
|
|
|
if (is_array($search_ret)) {
|
|
serendipity_plugin_api::hook_event('multilingual_strip_langs', $search_ret, array('category_name','category_description'));
|
|
foreach($search_ret AS $i => $entry) {
|
|
$ret[$assoc_ids[$entry['entryid']]]['categories'][] = $entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch a single entry by a specific condition
|
|
*
|
|
* @access public
|
|
* @param string The column to compare $val against (like 'id')
|
|
* @param string The value of the colum $key to compare with (like '4711')
|
|
* @param boolean Indicates if the full entry will be fetched (body+extended: TRUE), or only the body (FALSE). (Unused, keep for compat.)
|
|
* @param string Indicates whether drafts should be fetched
|
|
* @return
|
|
*/
|
|
function &serendipity_fetchEntry($key, $val, $full = true, $fetchDrafts = 'false') {
|
|
global $serendipity;
|
|
|
|
$cond = array();
|
|
$cond['and'] = " "; // intentional dummy string to attach dummy AND parts to the WHERE clauses
|
|
|
|
if ($fetchDrafts == 'false') {
|
|
$cond['and'] = " AND e.isdraft = 'false' " . (!serendipity_db_bool($serendipity['showFutureEntries']) ? " AND e.timestamp <= " . serendipity_db_time() : '');
|
|
}
|
|
|
|
if (isset($serendipity['GET']['adminModule']) && $serendipity['GET']['adminModule'] == 'entries' && !serendipity_checkPermission('adminEntriesMaintainOthers')) {
|
|
$cond['and'] = " AND e.authorid = '" . $serendipity['authorid'] . "'";
|
|
}
|
|
|
|
serendipity_ACL_SQL($cond, true);
|
|
|
|
serendipity_plugin_api::hook_event('frontend_fetchentry', $cond, array('noSticky' => true));
|
|
|
|
if (! array_key_exists('single_group', $cond)) {
|
|
$cond['single_group'] = '';
|
|
}
|
|
if (! array_key_exists('single_having', $cond)) {
|
|
$cond['single_having'] = '';
|
|
}
|
|
if (! array_key_exists('single_orderby', $cond)) {
|
|
$cond['single_orderby'] = '';
|
|
}
|
|
|
|
if (!isset($cond['joins'])) {
|
|
$cond['joins'] = '';
|
|
}
|
|
$querystring = "SELECT e.id,
|
|
e.title,
|
|
e.timestamp,
|
|
e.body,
|
|
e.comments,
|
|
e.trackbacks,
|
|
e.extended,
|
|
e.exflag,
|
|
e.authorid,
|
|
e.isdraft,
|
|
e.allow_comments,
|
|
e.last_modified,
|
|
e.moderate_comments,
|
|
a.realname AS author,
|
|
a.username AS loginname,
|
|
a.email
|
|
FROM
|
|
{$serendipity['dbPrefix']}entries e
|
|
LEFT JOIN {$serendipity['dbPrefix']}authors a
|
|
ON e.authorid = a.authorid
|
|
{$cond['joins']}
|
|
WHERE
|
|
e.$key " . ($key == 'id' ? '=' : 'LIKE') . " '" . serendipity_db_escape_string($val) . "'
|
|
{$cond['and']}
|
|
{$cond['single_group']}
|
|
{$cond['single_having']}
|
|
{$cond['single_orderby']}
|
|
LIMIT 1";
|
|
|
|
$ret =& serendipity_db_query($querystring, true, 'assoc');
|
|
|
|
if (is_array($ret)) {
|
|
$ret['categories'] =& serendipity_fetchEntryCategories($ret['id']);
|
|
$ret['properties'] =& serendipity_fetchEntryProperties($ret['id']);
|
|
$stack = array();
|
|
$stack[0] = &$ret;
|
|
$assoc_ids = array($ret['id'] => 0);
|
|
serendipity_plugin_api::hook_event('frontend_entryproperties', $stack, $assoc_ids);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Fetches additional entry properties for a specific entry ID
|
|
*
|
|
* @access public
|
|
* @param int The ID of the entry to fetch additonal data for
|
|
* @return array The array of given properties to an entry
|
|
*/
|
|
function &serendipity_fetchEntryProperties($id) {
|
|
global $serendipity;
|
|
|
|
$parts = array();
|
|
serendipity_plugin_api::hook_event('frontend_entryproperties_query', $parts);
|
|
|
|
$_properties =& serendipity_db_query("SELECT property, value FROM {$serendipity['dbPrefix']}entryproperties WHERE entryid = " . (int)$id . " " . $parts['and']);
|
|
if (!is_array($_properties)) {
|
|
$properties = array();
|
|
} else {
|
|
$properties =& $_properties;
|
|
}
|
|
|
|
$property = array();
|
|
foreach($properties AS $idx => $row) {
|
|
$property[$row['property']] =& $row['value'];
|
|
}
|
|
|
|
return $property;
|
|
}
|
|
|
|
/**
|
|
* Fetch a list of available categories for an author
|
|
*
|
|
* @access public
|
|
* @param mixed If set, the list of categories will be fetched according to the author id. If not set, all categories will be fetched. If set to "all", then all categories will be fetched.
|
|
* @param string Restrict the list to be returned to a specific category NAME.
|
|
* @param string The SQL query part for ORDER BY of the categories
|
|
* @param string The ACL artifact condition. If set to "write" only categories will be shown that the author can write to. If set to "read", only categories will be show that the author can read or write to.
|
|
* @return array Returns the array of categories
|
|
*/
|
|
function &serendipity_fetchCategories($authorid = null, $name = null, $order = null, $artifact_mode = 'write', $flat = false) {
|
|
global $serendipity;
|
|
|
|
if ($name === null) {
|
|
$name = '';
|
|
}
|
|
|
|
if ($order === null) {
|
|
$order = 'category_name ASC';
|
|
}
|
|
|
|
if (!isset($authorid) || $authorid === null) {
|
|
$authorid = ((isset($serendipity['authorid']) && !empty($serendipity['GET']['adminModule'])) ? $serendipity['authorid'] : 1);
|
|
}
|
|
|
|
if (isset($serendipity['authorid']) && !empty($serendipity['GET']['adminModule']) && $authorid != $serendipity['authorid'] && !serendipity_checkPermission('adminCategoriesMaintainOthers')) {
|
|
$authorid = $serendipity['authorid'];
|
|
}
|
|
|
|
$where = '';
|
|
|
|
if ($authorid === -1 OR $authorid === 0) {
|
|
$sql_groupid = '0';
|
|
} else {
|
|
$sql_groupid = 'ag.groupid';
|
|
}
|
|
|
|
if ($authorid != 'all' && is_numeric($authorid)) {
|
|
$sql_authorid = $authorid;
|
|
if (!serendipity_checkPermission('adminCategoriesMaintainOthers', $authorid)) {
|
|
$where = " WHERE (c.authorid = $authorid OR c.authorid = 0) ";
|
|
$where .= "OR (
|
|
acl.artifact_type = 'category'
|
|
AND acl.artifact_mode = '" . serendipity_db_escape_string($artifact_mode) . "'
|
|
) ";
|
|
|
|
}
|
|
} else {
|
|
$sql_authorid = 'c.authorid';
|
|
$where = '';
|
|
}
|
|
|
|
if (!empty($name)) {
|
|
if ($where == '') {
|
|
$where = ' WHERE ';
|
|
} else {
|
|
$where = ' AND ';
|
|
}
|
|
|
|
$where .= " c.category_name = '" . serendipity_db_escape_string($name) . "'";
|
|
}
|
|
|
|
if ($serendipity['dbType'] == 'postgres' ||
|
|
$serendipity['dbType'] == 'pdo-postgres') {
|
|
$group = '';
|
|
$distinct = 'DISTINCT';
|
|
} else {
|
|
$group = 'GROUP BY c.categoryid';
|
|
$distinct = '';
|
|
}
|
|
|
|
$querystring = "SELECT $distinct c.categoryid,
|
|
c.category_name,
|
|
c.category_icon,
|
|
c.category_description,
|
|
c.authorid,
|
|
c.category_left,
|
|
c.category_right,
|
|
c.parentid,
|
|
a.username,
|
|
a.username AS loginname,
|
|
a.realname
|
|
FROM {$serendipity['dbPrefix']}category AS c
|
|
LEFT OUTER JOIN {$serendipity['dbPrefix']}authors AS a
|
|
ON c.authorid = a.authorid
|
|
LEFT OUTER JOIN {$serendipity['dbPrefix']}authorgroups AS ag
|
|
ON ag.authorid = $sql_authorid
|
|
LEFT OUTER JOIN {$serendipity['dbPrefix']}access AS acl
|
|
ON ($sql_groupid = acl.groupid AND acl.artifact_id = c.categoryid)
|
|
$where
|
|
$group";
|
|
if (!empty($order)) {
|
|
$querystring .= "\n ORDER BY $order";
|
|
}
|
|
|
|
$ret =& serendipity_db_query($querystring);
|
|
if (is_string($ret)) {
|
|
echo "Query failed: $ret";
|
|
} else {
|
|
if ($flat) {
|
|
$cats = serendipity_walkRecursive($ret, 'categoryid', 'parentid', VIEWMODE_THREADED);
|
|
$flat_cats = array();
|
|
$flat_cats[0] = NO_CATEGORY;
|
|
serendipity_plugin_api::hook_event('multilingual_strip_langs',$cats, array('category_name','category_description'));
|
|
foreach($cats AS $catidx => $catdata) {
|
|
$flat_cats[$catdata['categoryid']] = str_repeat(' ', $catdata['depth']*2) . serendipity_specialchars($catdata['category_name']);
|
|
}
|
|
return $flat_cats;
|
|
} else {
|
|
serendipity_plugin_api::hook_event('multilingual_strip_langs',$ret, array('category_name','category_description'));
|
|
return $ret;
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Rebuild the Category Nested Set tree
|
|
*
|
|
* @access public
|
|
* @see Based on http://www.sitepoint.com/article/hierarchical-data-database/1
|
|
* @param int The ID of the parent category to rebuild
|
|
* @param int The ID of the next left category
|
|
* @return int Returns the new ID
|
|
*/
|
|
function serendipity_rebuildCategoryTree($parent = 0, $left = 0) {
|
|
global $serendipity;
|
|
$right = $left + 1;
|
|
|
|
$result = serendipity_db_query("SELECT categoryid FROM {$serendipity['dbPrefix']}category WHERE parentid = '" . (int)$parent . "'");
|
|
if ( is_array($result) ) {
|
|
foreach ( $result as $category ) {
|
|
$right = serendipity_rebuildCategoryTree($category['categoryid'], $right);
|
|
}
|
|
}
|
|
if ( $parent > 0 ) {
|
|
serendipity_db_query("UPDATE {$serendipity['dbPrefix']}category SET category_left='{$left}', category_right='{$right}' WHERE categoryid='{$parent}'");
|
|
}
|
|
|
|
return $right + 1;
|
|
}
|
|
|
|
/**
|
|
* Searches the list of entries by a specific term
|
|
*
|
|
* @todo: Allow to show results of staticpage plugins or others
|
|
* @access public
|
|
* @param string The searchterm (may contain wildcards)
|
|
* @param int Restrict the number of results [also uses $serendipity['GET']['page'] for pagination]
|
|
* @param array Add search Results at the top
|
|
* @return array Returns the superarray of entries found
|
|
*/
|
|
function &serendipity_searchEntries($term, $limit = '', $searchresults = '') {
|
|
global $serendipity;
|
|
static $log_queries = false;
|
|
|
|
if ($log_queries) {
|
|
$fp = fopen($serendipity['serendipityPath'] . '/archives/queries.csv', 'a');
|
|
fwrite($fp, date('Y-m-d H:i') . ';'
|
|
. $_SERVER['REMOTE_ADDR'] . ';'
|
|
. $term . "\n");
|
|
fclose($fp);
|
|
}
|
|
|
|
$orig_limit = $limit;
|
|
if ($limit == '') {
|
|
$limit = $serendipity['fetchLimit'];
|
|
}
|
|
|
|
if (isset($serendipity['GET']['page']) && $serendipity['GET']['page'] > 1 && !strstr($limit, ',')) {
|
|
$limit = serendipity_db_limit(($serendipity['GET']['page']-1) * $limit, $limit);
|
|
}
|
|
|
|
$limit = serendipity_db_limit_sql($limit);
|
|
|
|
$term = serendipity_db_escape_string($term);
|
|
$cond = array('having' => '');
|
|
$relevance_enabled = false;
|
|
if ($serendipity['dbType'] == 'postgres' ||
|
|
$serendipity['dbType'] == 'pdo-postgres') {
|
|
$cond['group'] = '';
|
|
$cond['distinct'] = 'DISTINCT';
|
|
|
|
$r = serendipity_db_query("SELECT count(routine_name) AS counter
|
|
FROM information_schema.routines
|
|
WHERE routine_name LIKE 'to_tsvector'
|
|
AND specific_catalog = '" . $serendipity['dbName'] . "'");
|
|
if (is_array($r) && $r[0]['counter'] > 0) {
|
|
$term = str_replace('&', '&', $term);
|
|
$cond['find_part'] = "(
|
|
to_tsvector('english', title) @@to_tsquery('$term') OR
|
|
to_tsvector('english', body) @@to_tsquery('$term') OR
|
|
to_tsvector('english', extended) @@to_tsquery('$term')
|
|
)";
|
|
} else {
|
|
$cond['find_part'] = "(title ILIKE '%$term%' OR body ILIKE '%$term%' OR extended ILIKE '%$term%')";
|
|
}
|
|
} elseif ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite' || $serendipity['dbType'] == 'sqlite3oo') {
|
|
// Very extensive SQLite search. There currently seems no other way to perform fulltext search in SQLite
|
|
// But it's better than no search at all :-D
|
|
$term = str_replace('*', '%', $term);
|
|
$cond['group'] = 'GROUP BY e.id';
|
|
$cond['distinct'] = '';
|
|
$term = mb_strtolower($term);
|
|
$cond['find_part'] = "(lower(title) LIKE '%$term%' OR lower(body) LIKE '%$term%' OR lower(extended) LIKE '%$term%')";
|
|
} else {
|
|
$cond['group'] = 'GROUP BY e.id';
|
|
$cond['distinct'] = '';
|
|
$term = str_replace('"', '"', $term);
|
|
$relevance_enabled = true;
|
|
if (preg_match('@["\+\-\*~<>\(\)]+@', $term)) {
|
|
$cond['find_part'] = "MATCH(title,body,extended) AGAINST('$term' IN BOOLEAN MODE)";
|
|
} else {
|
|
$cond['find_part'] = "MATCH(title,body,extended) AGAINST('$term')";
|
|
}
|
|
}
|
|
|
|
switch($serendipity['searchsort']) {
|
|
case 'relevance':
|
|
if ($relevance_enabled) {
|
|
$cond['searchorderby'] = $cond['find_part'] . " DESC";
|
|
} else {
|
|
$cond['searchorderby'] = "timestamp DESC";
|
|
}
|
|
break;
|
|
|
|
case 'timestamp':
|
|
default:
|
|
$cond['searchorderby'] = "timestamp DESC";
|
|
break;
|
|
}
|
|
|
|
$cond['and'] = " AND isdraft = 'false' " . (!serendipity_db_bool($serendipity['showFutureEntries']) ? " AND timestamp <= " . serendipity_db_time() : '');
|
|
serendipity_plugin_api::hook_event('frontend_fetchentries', $cond, array('source' => 'search', 'term' => $term));
|
|
serendipity_ACL_SQL($cond, 'limited');
|
|
|
|
if (!isset($cond['joins'])) {
|
|
$cond['joins'] = '';
|
|
}
|
|
if (!isset($cond['addkey'])) {
|
|
$cond['addkey'] = '';
|
|
}
|
|
$serendipity['fullCountQuery'] = "
|
|
FROM
|
|
{$serendipity['dbPrefix']}entries e
|
|
LEFT JOIN {$serendipity['dbPrefix']}authors a
|
|
ON e.authorid = a.authorid
|
|
LEFT JOIN {$serendipity['dbPrefix']}entrycat ec
|
|
ON e.id = ec.entryid
|
|
{$cond['joins']}
|
|
WHERE
|
|
({$cond['find_part']})
|
|
{$cond['and']}";
|
|
|
|
$querystring = "SELECT {$cond['distinct']}
|
|
{$cond['addkey']}
|
|
e.id,
|
|
e.authorid,
|
|
a.realname AS author,
|
|
e.allow_comments,
|
|
e.moderate_comments,
|
|
a.email,
|
|
e.timestamp,
|
|
e.comments,
|
|
e.title,
|
|
e.body,
|
|
e.extended,
|
|
e.trackbacks,
|
|
e.exflag,
|
|
e.isdraft,
|
|
e.last_modified,
|
|
a.username AS loginname
|
|
{$serendipity['fullCountQuery']}
|
|
{$cond['group']}
|
|
{$cond['having']}
|
|
ORDER BY {$cond['searchorderby']}
|
|
$limit";
|
|
|
|
$search =& serendipity_db_query($querystring);
|
|
|
|
//Add param searchresults at the top and remove duplicates.
|
|
if (is_array($searchresults)) {
|
|
$ids_current = array();
|
|
foreach($searchresults AS $idx => $data) {
|
|
$ids_current[$data['id']] = true;
|
|
}
|
|
|
|
foreach($search AS $idx => $data) {
|
|
if (isset($ids_current[$data['id']])) {
|
|
unset($search[$idx]);
|
|
}
|
|
}
|
|
$search = array_merge($searchresults, $search);
|
|
}
|
|
|
|
if (is_array($search)){
|
|
serendipity_fetchEntryData($search);
|
|
}
|
|
|
|
return $search;
|
|
}
|
|
|
|
/**
|
|
* Creates the Footer below the entries, with pagination options and parses it to Smarty
|
|
*
|
|
* The list of total entries is calculated from the serendipity_getTotelEntries() function
|
|
*
|
|
* @param string suffix for URLs
|
|
* @param int Amount of total entries
|
|
* @access public
|
|
* @see serendipity_getTotalEntries()
|
|
* @return null
|
|
*/
|
|
function serendipity_printEntryFooter($suffix = '.html', $totalEntries = null) {
|
|
global $serendipity;
|
|
|
|
if ($totalEntries === null) {
|
|
$totalEntries = serendipity_getTotalEntries();
|
|
}
|
|
|
|
$limits = explode(',', $serendipity['fetchLimit']);
|
|
if (!empty($limits[1])) {
|
|
$limit = (int)$limits[1];
|
|
} else {
|
|
$limit = (int)$limits[0];
|
|
}
|
|
$totalPages = ceil($totalEntries / $limit);
|
|
|
|
if ($totalPages <= 0 ) {
|
|
$totalPages = 1;
|
|
}
|
|
|
|
if (!isset($serendipity['GET']['page']) && serendipity_db_bool($serendipity['archiveSortStable']) && $serendipity['GET']['action'] != 'search') {
|
|
$serendipity['GET']['page'] = $totalPages;
|
|
} else if (!isset($serendipity['GET']['page'])) {
|
|
$serendipity['GET']['page'] = 1;
|
|
}
|
|
|
|
if ($serendipity['GET']['page'] > 1) {
|
|
$uriArguments = $serendipity['uriArguments'];
|
|
$uriArguments[] = 'P'. ($serendipity['GET']['page'] - 1);
|
|
$serendipity['smarty']->assign('footer_prev_page', serendipity_rewriteURL(str_replace('//', '/', implode('/', $uriArguments)) . $suffix));
|
|
} else {
|
|
$serendipity['smarty']->assign('footer_prev_page', false);
|
|
}
|
|
|
|
$uriArguments = $serendipity['uriArguments'];
|
|
$uriArguments[] = 'P%s';
|
|
$serendipity['smarty']->assign('footer_totalEntries', $totalEntries);
|
|
$serendipity['smarty']->assign('footer_totalPages', $totalPages);
|
|
$serendipity['smarty']->assign('footer_currentPage', $serendipity['GET']['page']);
|
|
$serendipity['smarty']->assign('footer_pageLink', str_replace('%2A', '*', serendipity_rewriteURL(implode('/', $uriArguments) . $suffix)));
|
|
$serendipity['smarty']->assign('footer_info', sprintf(PAGE_BROWSE_ENTRIES, (int)$serendipity['GET']['page'], $totalPages, $totalEntries));
|
|
|
|
if ($serendipity['GET']['page'] < $totalPages) {
|
|
$uriArguments = $serendipity['uriArguments'];
|
|
$uriArguments[] = 'P'. ($serendipity['GET']['page'] + 1);
|
|
$serendipity['smarty']->assign('footer_next_page', serendipity_rewriteURL(str_replace('//', '/', implode('/', $uriArguments)) . $suffix));
|
|
} else {
|
|
$serendipity['smarty']->assign('footer_next_page', false);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Calculates the amount of available entries.
|
|
*
|
|
* This function uses the SQL query portion of the central serendipity_fetchEntries() query
|
|
* and modifies it with different GROUP statements to calculate the number of entries.
|
|
*
|
|
* @access public
|
|
* @see serendipity_fetchEntries()
|
|
* @return int The number of total entries
|
|
*/
|
|
function serendipity_getTotalEntries() {
|
|
global $serendipity;
|
|
|
|
// The unique query condition was built previously in serendipity_fetchEntries()
|
|
if ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite' || $serendipity['dbType'] == 'sqlite3oo') {
|
|
$querystring = "SELECT count(e.id) {$serendipity['fullCountQuery']} GROUP BY e.id";
|
|
} else {
|
|
$querystring = "SELECT count(distinct e.id) {$serendipity['fullCountQuery']}";
|
|
}
|
|
|
|
if ($serendipity['fullCountQuery'] == '') {
|
|
return 0;
|
|
}
|
|
$query =& serendipity_db_query($querystring);
|
|
|
|
if (is_array($query) && isset($query[0])) {
|
|
if ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite' || $serendipity['dbType'] == 'sqlite3oo') {
|
|
return count($query);
|
|
} else {
|
|
return $query[0][0];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Passes the list of fetched entries from serendipity_fetchEntries() on to the Smarty layer
|
|
*
|
|
* This function contains all the core logic to group and prepare entries to be shown in your
|
|
* $entries.tpl template. It groups them by date, so that you can easily loop on the set of
|
|
* entries.
|
|
* This function is not only used for printing all entries, but also for printing individual
|
|
* entries.
|
|
* Several central Event hooks are executed here for the whole page flow, like header+footer data.
|
|
*
|
|
* @see serendipity_fetchEntries()
|
|
* @see serendipity_searchEntries()
|
|
* @access public
|
|
* @param array The array of entries with all of its data
|
|
* @param boolean Toggle whether the extended portion of an entry is requested (via $serendipity['GET']['id'] single entry view)
|
|
* @param boolean Indicates if this is a preview
|
|
* @param string The name of the SMARTY block that this gets parsed into
|
|
* @param boolean Indicates whether the assigned smarty variables should be parsed. When set to "return", no smarty parsing is done.
|
|
* @param boolean Indicates whether to apply footer/header event hooks
|
|
* @param boolean Indicates whether the pagination footer should be displayed
|
|
* @param mixed Indicates whether the input $entries array is already grouped in preparation for the smarty $entries output array [TRUE], or if it shall be grouped by date [FALSE] or if a plugin hook shall be executed to modify data ['plugin']. This setting can also be superseded by a 'entry_display' hook.
|
|
* @return
|
|
*/
|
|
function serendipity_printEntries($entries, $extended = 0, $preview = false, $smarty_block = 'ENTRIES', $smarty_fetch = true, $use_hooks = true, $use_footer = true, $use_grouped_array = false) {
|
|
global $serendipity;
|
|
|
|
if (!is_object($serendipity['smarty'] ?? null)) {
|
|
serendipity_smarty_init(); // if not set, start Smarty templating to avoid member function "method()" on a non-object errors (was draft preview error, now at line 1239)
|
|
}
|
|
|
|
if ($use_hooks) {
|
|
$addData = array('extended' => $extended, 'preview' => $preview);
|
|
serendipity_plugin_api::hook_event('entry_display', $entries, $addData);
|
|
|
|
if (isset($entries['clean_page']) && $entries['clean_page'] === true) {
|
|
if ($serendipity['view'] == '404') {
|
|
$serendipity['view'] = 'plugin';
|
|
}
|
|
|
|
$serendipity['smarty']->assign(array(
|
|
'plugin_clean_page' => true,
|
|
'view' => $serendipity['view'])
|
|
);
|
|
serendipity_smarty_fetch($smarty_block, 'entries.tpl', true);
|
|
return; // no display of this item
|
|
}
|
|
}
|
|
|
|
// We shouldn't return here, because we want Smarty to handle the output
|
|
if (!is_array($entries) || $entries[0] == false || !isset($entries[0]['timestamp'])) {
|
|
$entries = array();
|
|
}
|
|
|
|
// A plugin executed in entry_display should be able to change the way of ordering entries. Forward-Thinking. ;)
|
|
if (isset($entries['use_grouped_array'])) {
|
|
$use_grouped_array = $entries['use_grouped_array'];
|
|
}
|
|
|
|
if ($use_grouped_array === false) {
|
|
// Use grouping by date (default)
|
|
$dategroup = array();
|
|
$num_entries = count($entries);
|
|
foreach ($entries as $i => &$entry) {
|
|
if ($i !== 'id') {
|
|
if (!empty($entry['properties']['ep_is_sticky']) && serendipity_db_bool($entry['properties']['ep_is_sticky'])) {
|
|
$entry['is_sticky'] = true;
|
|
$key = 'sticky';
|
|
} else {
|
|
$key = date('Ymd', serendipity_serverOffsetHour($entry['timestamp']));
|
|
}
|
|
|
|
$dategroup[$key]['date'] = $entry['timestamp'];
|
|
$dategroup[$key]['is_sticky'] = (isset($entry['is_sticky']) && (serendipity_db_bool($entry['is_sticky']) ? true : false));
|
|
$dategroup[$key]['entries'][] = &$entry;
|
|
} else {
|
|
$num_entries--;
|
|
}
|
|
}
|
|
} elseif ($use_grouped_array === 'plugin') {
|
|
// Let a plugin do the grouping
|
|
serendipity_plugin_api::hook_event('entry_groupdata', $entries);
|
|
$dategroup =& $entries;
|
|
} else {
|
|
$dategroup =& $entries;
|
|
}
|
|
|
|
foreach($dategroup as $dategroup_idx => $properties) {
|
|
foreach($properties['entries'] as $x => $_entry) {
|
|
if ($smarty_fetch === 'return') {
|
|
$entry = &$dategroup[$dategroup_idx]['entries'][$x]; // PHP4 Compat
|
|
} else {
|
|
// DISABLED - made problems with custom plugins
|
|
// $entry = &$properties['entries'][$x]; // PHP4 Compat
|
|
$entry = &$dategroup[$dategroup_idx]['entries'][$x]; // PHP4 Compat
|
|
}
|
|
|
|
if (!empty($entry['properties']['ep_cache_body'])) {
|
|
$entry['pre_body'] = $entry['body'] ?? null;
|
|
$entry['body'] = &$entry['properties']['ep_cache_body'];
|
|
$entry['is_cached'] = true;
|
|
}
|
|
|
|
//--JAM: Highlight-span search terms
|
|
if ($serendipity['action'] ?? '' == 'search') {
|
|
$searchterms = str_replace('"', '', $serendipity['GET']['searchterms']);
|
|
$searchterms = explode($searchterms, ' ');
|
|
foreach($searchterms as $searchdx => $searchterm) {
|
|
$searchclass = "foundterm foundterm".$searchdx;
|
|
$entry['body'] = str_replace($searchterm, '<span class="'.$searchclass.'">' . $searchterm . '</span>', $entry['body']);
|
|
}
|
|
}
|
|
|
|
if (!empty($entry['properties']['ep_cache_extended'])) {
|
|
$entry['pre_extended'] = $entry['extended'] ?? null;
|
|
$entry['extended'] = &$entry['properties']['ep_cache_extended'];
|
|
$entry['is_cached'] = true;
|
|
}
|
|
|
|
if ($preview) {
|
|
$entry['author'] = $entry['realname'];
|
|
$entry['authorid'] = $serendipity['authorid'];
|
|
}
|
|
|
|
$addData = array('from' => 'functions_entries:printEntries');
|
|
if ($entry['is_cached'] ?? false) {
|
|
$addData['no_scramble'] = true;
|
|
}
|
|
serendipity_plugin_api::hook_event('frontend_display', $entry, $addData);
|
|
|
|
if ($preview) {
|
|
$entry['author'] = $entry['realname'];
|
|
$entry['authorid'] = $serendipity['authorid'];
|
|
}
|
|
|
|
$entry['author'] = serendipity_specialchars($entry['author']);
|
|
|
|
$authorData = array(
|
|
'authorid' => $entry['authorid'],
|
|
'username' => $entry['loginname'] ?? null,
|
|
'email' => $entry['email'] ?? null,
|
|
'realname' => $entry['author']
|
|
);
|
|
|
|
$entry['link'] = serendipity_archiveURL($entry['id'], $entry['title'], 'serendipityHTTPPath', true, array('timestamp' => $entry['timestamp']));
|
|
$entry['commURL'] = serendipity_archiveURL($entry['id'], $entry['title'], 'baseURL', false, array('timestamp' => $entry['timestamp']));
|
|
$entry['html_title'] = $entry['title'];
|
|
$entry['title'] = serendipity_specialchars($entry['title']);
|
|
|
|
$entry['title_rdf'] = preg_replace('@-{2,}@', '-', $entry['html_title']);
|
|
$entry['rdf_ident'] = serendipity_archiveURL($entry['id'], $entry['title_rdf'], 'baseURL', true, array('timestamp' => $entry['timestamp']));
|
|
$entry['link_rdf'] = serendipity_rewriteURL(PATH_FEEDS . '/ei_'. $entry['id'] .'.rdf');
|
|
$entry['title_rdf'] = serendipity_specialchars($entry['title_rdf']);
|
|
|
|
$formToken = serendipity_setFormToken('url');
|
|
$entry['link_allow_comments'] = $serendipity['baseURL'] . 'comment.php?serendipity[switch]=enable&serendipity[entry]=' . $entry['id'] . '&' . $formToken;
|
|
$entry['link_deny_comments'] = $serendipity['baseURL'] . 'comment.php?serendipity[switch]=disable&serendipity[entry]=' . $entry['id'] . '&' . $formToken;
|
|
$entry['allow_comments'] = serendipity_db_bool($entry['allow_comments']);
|
|
$entry['moderate_comments'] = serendipity_db_bool($entry['moderate_comments'] ?? null);
|
|
if (defined('VIEWMODE_LINEAR')) {
|
|
$entry['viewmode'] = (($serendipity['GET']['cview'] ?? null) == VIEWMODE_LINEAR ? VIEWMODE_LINEAR : VIEWMODE_THREADED);
|
|
}
|
|
$entry['link_popup_comments'] = $serendipity['serendipityHTTPPath'] .'comment.php?serendipity[entry_id]='. $entry['id'] .'&serendipity[type]=comments';
|
|
$entry['link_popup_trackbacks'] = $serendipity['serendipityHTTPPath'] .'comment.php?serendipity[entry_id]='. $entry['id'] .'&serendipity[type]=trackbacks';
|
|
$entry['link_edit'] = $serendipity['baseURL'] .'serendipity_admin.php?serendipity[action]=admin&serendipity[adminModule]=entries&serendipity[adminAction]=edit&serendipity[id]='. $entry['id'];
|
|
$entry['link_trackback'] = $serendipity['baseURL'] .'comment.php?type=trackback&entry_id='. $entry['id'];
|
|
if (defined('VIEWMODE_THREADED')) {
|
|
$entry['link_viewmode_threaded'] = $serendipity['serendipityHTTPPath'] . $serendipity['indexFile'] .'?url='. $entry['commURL'] .'&serendipity[cview]='. VIEWMODE_THREADED;
|
|
}
|
|
if (defined('VIEWMODE_LINEAR')) {
|
|
$entry['link_viewmode_linear'] = $serendipity['serendipityHTTPPath'] . $serendipity['indexFile'] .'?url='. $entry['commURL'] .'&serendipity[cview]='. VIEWMODE_LINEAR;
|
|
}
|
|
$entry['link_author'] = serendipity_authorURL($authorData);
|
|
|
|
if (is_array($entry['categories'] ?? null)) {
|
|
foreach ($entry['categories'] as $k => $v) {
|
|
if (!isset($entry['categories'][$k]['category_link'])) {
|
|
$entry['categories'][$k]['category_link'] = serendipity_categoryURL($entry['categories'][$k]);
|
|
}
|
|
}
|
|
} else {
|
|
$entry['categories'] = null;
|
|
}
|
|
|
|
if (strlen($entry['extended'] ?? null)) {
|
|
$entry['has_extended'] = true;
|
|
} else {
|
|
$entry['has_extended'] = false;
|
|
}
|
|
|
|
if (! isset($entry['add_footer'])) {
|
|
$entry['add_footer'] = '';
|
|
}
|
|
|
|
if (isset($entry['exflag']) && $entry['exflag'] && ($extended || $preview)) {
|
|
$entry['is_extended'] = true;
|
|
} else {
|
|
$entry['is_extended'] = false;
|
|
}
|
|
|
|
if (serendipity_db_bool($entry['allow_comments']) || !isset($entry['allow_comments']) || $entry['comments'] > 0) {
|
|
$entry['has_comments'] = true;
|
|
$entry['label_comments'] = $entry['comments'] == 1 ? COMMENT : COMMENTS;
|
|
}
|
|
|
|
if (serendipity_db_bool($entry['allow_comments']) || !isset($entry['allow_comments']) || $entry['trackbacks'] > 0) {
|
|
$entry['has_trackbacks'] = true;
|
|
$entry['label_trackbacks'] = $entry['trackbacks'] == 1 ? TRACKBACK : TRACKBACKS;
|
|
}
|
|
|
|
if ($_SESSION['serendipityAuthedUser'] === true && ($_SESSION['serendipityAuthorid'] == $entry['authorid'] || serendipity_checkPermission('adminEntriesMaintainOthers'))) {
|
|
$entry['is_entry_owner'] = true;
|
|
}
|
|
|
|
$entry['display_dat'] = '';
|
|
serendipity_plugin_api::hook_event('frontend_display:html:per_entry', $entry);
|
|
$entry['plugin_display_dat'] =& $entry['display_dat'];
|
|
|
|
if ($preview) {
|
|
ob_start();
|
|
serendipity_plugin_api::hook_event('backend_preview', $entry);
|
|
$entry['backend_preview'] = ob_get_contents();
|
|
ob_end_clean();
|
|
} else {
|
|
$entry['backend_preview'] = null;
|
|
}
|
|
|
|
/* IF WE ARE DISPLAYING A FULL ENTRY */
|
|
if (isset($serendipity['GET']['id'])) {
|
|
$comment_add_data = array(
|
|
'comments_messagestack' => (isset($serendipity['messagestack']['comments']) ? (array)$serendipity['messagestack']['comments'] : array()),
|
|
'is_comment_added' => (isset($serendipity['GET']['csuccess']) && $serendipity['GET']['csuccess'] == 'true' ? true: false),
|
|
'is_comment_moderate' => (isset($serendipity['GET']['csuccess']) && $serendipity['GET']['csuccess'] == 'moderate' ? true: false)
|
|
);
|
|
|
|
if ($serendipity['serendipityAuthedUser'] ?? null === true && !isset($serendipity['POST']['preview'])) {
|
|
$userData = $serendipity['POST'];
|
|
if (empty($userData['name'])) {
|
|
$userData['name'] = $serendipity['serendipityRealname'];
|
|
}
|
|
if (empty($userData['email'])) {
|
|
$userData['email'] = $serendipity['email'];
|
|
}
|
|
} else {
|
|
$userData = $serendipity['POST'];
|
|
}
|
|
|
|
$serendipity['smarty']->assign($comment_add_data);
|
|
serendipity_displayCommentForm(
|
|
$entry['id'],
|
|
serendipity_rewriteURL($entry['commURL'],'baseURL'),
|
|
true,
|
|
$userData,
|
|
true,
|
|
serendipity_db_bool($entry['moderate_comments']),
|
|
$entry
|
|
);
|
|
// Make $entry available for the index.tpl, it uses it to generate the canonical meta tag
|
|
$serendipity['smarty']->assign(array(
|
|
'entry' => $entry,
|
|
));
|
|
} // END FULL ENTRY LOGIC
|
|
} // end foreach-loop (entries)
|
|
} // end foreach-loop (dates)
|
|
|
|
if (!isset($serendipity['GET']['id']) &&
|
|
(!isset($serendipity['hidefooter']) || $serendipity['hidefooter'] == false) &&
|
|
($num_entries <= $serendipity['fetchLimit']) &&
|
|
$use_footer) {
|
|
serendipity_printEntryFooter();
|
|
}
|
|
|
|
if ($smarty_fetch === 'return') {
|
|
return $dategroup;
|
|
}
|
|
|
|
$serendipity['smarty']->assignByRef('entries', $dategroup);
|
|
unset($entries, $dategroup);
|
|
|
|
$serendipity['smarty']->assign(array(
|
|
'is_preview' => $preview
|
|
));
|
|
|
|
if (isset($serendipity['short_archives']) && $serendipity['short_archives']) {
|
|
return serendipity_smarty_fetch($smarty_block, 'entries_summary.tpl', true);
|
|
} elseif ($smarty_fetch == true) {
|
|
return serendipity_smarty_fetch($smarty_block, 'entries.tpl', true, $preview);
|
|
}
|
|
|
|
} // end function serendipity_printEntries
|
|
|
|
|
|
/**
|
|
* Inserts a new entry into the database or updates an existing entry
|
|
*
|
|
* Another central function, that parses, prepares and commits changes to an entry
|
|
*
|
|
* @access public
|
|
* @param array The new/modified entry data.
|
|
* @return mixed Integer with new entry ID if successfull, a string or array if error(s).
|
|
*/
|
|
function serendipity_updertEntry($entry) {
|
|
global $serendipity;
|
|
|
|
include_once S9Y_INCLUDE_PATH . 'include/functions_entries_admin.inc.php';
|
|
|
|
$errors = array();
|
|
serendipity_plugin_api::hook_event('backend_entry_updertEntry', $errors, $entry);
|
|
if (count($errors) > 0) {
|
|
// Return error message(s)
|
|
return implode("\n", $errors);
|
|
}
|
|
|
|
serendipity_plugin_api::hook_event('backend_entry_presave', $entry);
|
|
|
|
$categories = $entry['categories'];
|
|
unset($entry['categories']);
|
|
|
|
if (array_key_exists('had_categories', $entry)) {
|
|
$had_categories = $entry['had_categories'];
|
|
unset($entry['had_categories']);
|
|
}
|
|
|
|
$newEntry = 0;
|
|
$exflag = 0;
|
|
|
|
if (isset($entry['properties'])) {
|
|
unset($entry['properties']);
|
|
}
|
|
|
|
if (!is_numeric($entry['timestamp'])) {
|
|
$entry['timestamp'] = time();
|
|
}
|
|
|
|
/* WYSIWYG-editor inserts empty ' ' for extended body; this is reversed here */
|
|
if (isset($entry['extended']) && (trim($entry['extended']) == '' || trim($entry['extended']) == '<br />' || trim($entry['extended']) == '<p></p>' || str_replace(array("\r", "\n", "\t", "\0", "<br />", "<p>", "</p>", "<br>"), array('', '', '', '', '', '', '', ''), trim($entry['extended'])) == '')) {
|
|
$entry['extended'] = '';
|
|
}
|
|
|
|
if (strlen($entry['extended'])) {
|
|
$exflag = 1;
|
|
}
|
|
|
|
$entry['exflag'] = $exflag;
|
|
|
|
if (!is_numeric($entry['id'])) {
|
|
/* we need to insert */
|
|
|
|
unset($entry['id']);
|
|
$entry['comments'] = 0;
|
|
|
|
if (!isset($entry['last_modified']) || !is_numeric($entry['last_modified'])) {
|
|
$entry['last_modified'] = $entry['timestamp'];
|
|
}
|
|
|
|
// New entries need an author
|
|
$entry['author'] = $serendipity['user'];
|
|
if (!isset($entry['authorid']) || empty($entry['authorid'])) {
|
|
$entry['authorid'] = $serendipity['authorid'];
|
|
}
|
|
|
|
if (!$_SESSION['serendipityRightPublish']) {
|
|
$entry['isdraft'] = 'true';
|
|
}
|
|
|
|
if (!isset($entry['allow_comments'])) {
|
|
$entry['allow_comments'] = 'false';
|
|
}
|
|
if (!isset($entry['moderate_comments'])) {
|
|
$entry['moderate_comments'] = 'false';
|
|
}
|
|
|
|
$res = serendipity_db_insert('entries', $entry);
|
|
|
|
if ($res) {
|
|
$entry['id'] = $serendipity['lastSavedEntry'] = serendipity_db_insert_id('entries', 'id');
|
|
if (is_array($categories)) {
|
|
foreach ($categories as $cat) {
|
|
if (is_numeric($cat)) {
|
|
serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}entrycat (entryid, categoryid) VALUES ({$entry['id']}, {$cat})");
|
|
} elseif (is_array($cat) && !empty($cat['categoryid'])) {
|
|
serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}entrycat (entryid, categoryid) VALUES ({$entry['id']}, " . (int)$cat['categoryid'] . ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
serendipity_insertPermalink($entry);
|
|
} else {
|
|
//Some error message here
|
|
return ENTRIES_NOT_SUCCESSFULLY_INSERTED;
|
|
}
|
|
$newEntry = 1;
|
|
|
|
} else {
|
|
/* we need to update */
|
|
|
|
// Get settings from entry if already in DB, which should not be alterable with POST methods
|
|
$_entry = serendipity_fetchEntry('id', $entry['id'], 1, 1);
|
|
$entry['authorid'] = $_entry['authorid'];
|
|
|
|
if (isset($serendipity['GET']['adminModule']) && $serendipity['GET']['adminModule'] == 'entries' && $entry['authorid'] != $serendipity['authorid'] && !serendipity_checkPermission('adminEntriesMaintainOthers')) {
|
|
// Only chiefs and admins can change other's entry. Else update fails.
|
|
return;
|
|
}
|
|
|
|
if (!$_SESSION['serendipityRightPublish']) {
|
|
$entry['isdraft'] = 'true';
|
|
}
|
|
|
|
if (is_array($categories)) {
|
|
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}entrycat WHERE entryid={$entry['id']}");
|
|
foreach ($categories as $cat) {
|
|
serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}entrycat (entryid, categoryid) VALUES ({$entry['id']}, " . (int)$cat . ")");
|
|
}
|
|
} elseif ($had_categories) {
|
|
// This case actually only happens if an existing entry is edited, and its category assignments are all removed.
|
|
// This field is set as a HIDDEN input field in the admin/entries.tpl template.
|
|
// This is more of a hotfix. In the future it should be thoroughly checked whether calls to serendipity_updertEntry()
|
|
// really always carry through existing categories. We do not want to accidentally remove category assignments,
|
|
// just because a plugin or so passes an incomplete $entry structure to this function.
|
|
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}entrycat WHERE entryid={$entry['id']}");
|
|
}
|
|
|
|
if ($entry['isdraft'] === 0) {
|
|
$entry['isdraft'] = 'false'; // this needs to be set as string, which might be missing when being published from the dashboard
|
|
|
|
foreach (array('title', 'body', 'extended', 'author') as $required_field) {
|
|
// dashboard publishing a draft does not pass title, body, extended, and author, so we need to set it here
|
|
if (!isset($entry[$required_field])) {
|
|
$entry[$required_field] = $_entry[$required_field];
|
|
}
|
|
}
|
|
}
|
|
|
|
$entry['last_modified'] = time();
|
|
|
|
$res = serendipity_db_update('entries', array('id' => $entry['id']), $entry);
|
|
$newEntry = 0;
|
|
serendipity_updatePermalink($entry);
|
|
}
|
|
|
|
if (is_string($res)) {
|
|
return $res;
|
|
}
|
|
|
|
// Reset session data, so that a reload to this frame should not happen!
|
|
$_SESSION['save_entry']['id'] = (int)$entry['id'];
|
|
|
|
if (!serendipity_db_bool($entry['isdraft'])) {
|
|
serendipity_plugin_api::hook_event('frontend_display', $entry, array('no_scramble' => true, 'from' => 'functions_entries:updertEntry'));
|
|
$drafted_entry = $entry;
|
|
}
|
|
|
|
if (!serendipity_db_bool($entry['isdraft']) && $entry['timestamp'] <= serendipity_serverOffsetHour()) {
|
|
// When saving an entry, first all references need to be gathered. But trackbacks to them
|
|
// shall only be send at the end of the execution flow. However, certain plugins depend on
|
|
// the existance of handled references. Thus we store the current references at this point,
|
|
// execute the plugins and then reset the found references to the original state.
|
|
serendipity_handle_references($entry['id'], $serendipity['blogTitle'], $drafted_entry['title'], $drafted_entry['body'] . $drafted_entry['extended'], true);
|
|
}
|
|
|
|
// Send publish tags if either a new article has been inserted from scratch, or if the entry was previously
|
|
// stored as draft and is now published
|
|
$entry['categories'] =& $categories;
|
|
if (!serendipity_db_bool($entry['isdraft']) && ($newEntry || serendipity_db_bool($_entry['isdraft']))) {
|
|
serendipity_plugin_api::hook_event('backend_publish', $entry, $newEntry);
|
|
} else {
|
|
serendipity_plugin_api::hook_event('backend_save', $entry, $newEntry);
|
|
}
|
|
|
|
if (!serendipity_db_bool($entry['isdraft']) && $entry['timestamp'] <= serendipity_serverOffsetHour()) {
|
|
// Now that plugins are executed, we go ahead into the Temple of Doom and send possibly failing trackbacks.
|
|
// First, original list of references is restored (inside the function call)
|
|
serendipity_handle_references($entry['id'], $serendipity['blogTitle'], $drafted_entry['title'], $drafted_entry['body'] . $drafted_entry['extended'], false);
|
|
}
|
|
|
|
$cache = ContentCache::getInstance();
|
|
$cache->clearCache();
|
|
return (int)$entry['id'];
|
|
}
|
|
|
|
/**
|
|
* Delete an entry and everything that belongs to it (comments)
|
|
*
|
|
* @access public
|
|
* @param int The Entry ID to delete
|
|
* @return mixed FALSE or NULL on error
|
|
*/
|
|
function serendipity_deleteEntry($id) {
|
|
global $serendipity;
|
|
|
|
if (!is_numeric($id)) {
|
|
return false;
|
|
}
|
|
|
|
// Purge the daily/monthly entries so they can be rebuilt
|
|
$result = serendipity_db_query("SELECT timestamp, authorid FROM {$serendipity['dbPrefix']}entries WHERE id = '". (int)$id ."'", true);
|
|
|
|
if ($result[1] != $serendipity['authorid'] && !serendipity_checkPermission('adminEntriesMaintainOthers')) {
|
|
// Only admins and chief users can delete entries which do not belong to the author
|
|
return;
|
|
}
|
|
|
|
serendipity_plugin_api::hook_event('backend_delete_entry', $id);
|
|
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}entries WHERE id=$id");
|
|
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}entrycat WHERE entryid=$id");
|
|
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}entryproperties WHERE entryid=$id");
|
|
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}comments WHERE entry_id=$id");
|
|
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}references WHERE entry_id='$id' AND type = ''");
|
|
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}permalinks WHERE entry_id='$id'");
|
|
$cache = ContentCache::getInstance();
|
|
$cache->clearCache();
|
|
}
|
|
|
|
/**
|
|
* Return HTML containing a list of categories
|
|
*
|
|
* Prints a list of categories for use in forms, the sidebar, or whereever...
|
|
*
|
|
* @access public
|
|
* @param array An array of categories, typically gathered by serendipity_fetchCategories()
|
|
* @param array An array which holds IDs which are meant to be selected within a HTML SELECT form field [used for recursion]
|
|
* @param int The type of category list (0: HTML span list, 1/2: <option>s, 3: HTML Div list, 4: CSV data) to return
|
|
* @param int The parent ID of a category [for recursion]
|
|
* @param int The current nesting level [for recursion]
|
|
* @param string Tells the function, whether or not to display the XML button for each category.
|
|
* If empty, no links to the xml feeds will be displayed; If you want to, you can
|
|
* pass an image here (this setting is only used, when type==3).
|
|
* @param string The character to use for blank indenting
|
|
* @see serendipity_fetchCategories()
|
|
*/
|
|
function serendipity_generateCategoryList($cats, $select = array(0), $type = 0, $id = 0, $level = 0, $xmlImg = '', $blank_char = ' ') {
|
|
global $serendipity;
|
|
|
|
if ( !is_array($cats) || !count($cats) )
|
|
return;
|
|
|
|
$ret = '';
|
|
foreach ($cats as $cat) {
|
|
if ($cat['parentid'] == $id) {
|
|
switch ($type) {
|
|
case 0:
|
|
$ret .= str_repeat(' ', $level * 2).'• <span class="block_level" id="catItem_' . $cat['categoryid'] . '"' . (($cat['categoryid'] && in_array($cat['categoryid'], $select)) ? ' selected="selected"' : '') . '><a href="?serendipity[adminModule]=category&serendipity[cat][catid]=' . $cat['categoryid'] . '">' . (!empty($cat['category_icon']) ? '<img style="vertical-align: middle;" src="' . $cat['category_icon'] . '" border="0" alt="' . $cat['category_name'] . '"/> ' : '') . serendipity_specialchars($cat['category_name']) . (!empty($cat['category_description']) ? ' - ' . serendipity_specialchars($cat['category_description']) : '') . '</a></span>';
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
$ret .= '<option value="' . $cat['categoryid'] . '"' . (($cat['categoryid'] && in_array($cat['categoryid'], $select)) ? ' selected="selected"' : '') . '>';
|
|
$ret .= str_repeat(' ', $level * 2) . serendipity_specialchars($cat['category_name']) . ($type == 1 && !empty($cat['category_description']) ? (' - ' . serendipity_specialchars($cat['category_description'])) : '');
|
|
$ret .= '</option>';
|
|
break;
|
|
case 3:
|
|
$category_id = serendipity_makeFilename($cat['category_name']);
|
|
if (!empty($xmlImg)) {
|
|
$ret .= sprintf(
|
|
'<div style="padding-bottom: 2px;">' .
|
|
'<a href="%s" title="%s"><img alt="xml" src="%s" style="vertical-align: bottom; display: inline; border: 0px" /></a> %s' .
|
|
'<a href="%s" title="%s">%s</a>' .
|
|
'</div>',
|
|
$serendipity['serendipityHTTPPath'] . 'rss.php?category=' . $cat['categoryid'] . '_' . $category_id,
|
|
serendipity_specialchars($cat['category_description']),
|
|
$xmlImg,
|
|
str_repeat(' ', $level * 3),
|
|
serendipity_categoryURL($cat, 'serendipityHTTPPath'),
|
|
serendipity_specialchars($cat['category_description']),
|
|
serendipity_specialchars($cat['category_name']));
|
|
} else {
|
|
$ret .= sprintf(
|
|
'<span class="block_level">%s<a href="%s" title="%s">%s</a></span>',
|
|
str_repeat(' ', $level * 3),
|
|
serendipity_categoryURL($cat, 'serendipityHTTPPath'),
|
|
serendipity_specialchars($cat['category_description']),
|
|
serendipity_specialchars($cat['category_name']));
|
|
}
|
|
break;
|
|
case 4:
|
|
$ret .= $cat['categoryid'] . '|||' . str_repeat($blank_char, $level * 2) . $cat['category_name'] . '@@@';
|
|
break;
|
|
}
|
|
$ret .= serendipity_generateCategoryList($cats, $select, $type, $cat['categoryid'], $level + 1, $xmlImg, $blank_char);
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Set category associations of a specific entry
|
|
*
|
|
* @access public
|
|
* @param int The ID of the entry
|
|
* @param array An array of category IDs that this entry is associated to.
|
|
* @return null
|
|
*/
|
|
function serendipity_updateEntryCategories($postid, $categories) {
|
|
global $serendipity;
|
|
|
|
if (!$postid || !$categories) {
|
|
return;
|
|
}
|
|
|
|
$query = "DELETE FROM $serendipity[dbPrefix]entrycat WHERE entryid = " . (int)$postid;
|
|
serendipity_db_query($query);
|
|
|
|
if (!is_array($categories)) {
|
|
$categories = array(0 => $categories);
|
|
}
|
|
|
|
foreach($categories AS $idx => $cat) {
|
|
$query = "INSERT INTO $serendipity[dbPrefix]entrycat (categoryid, entryid) VALUES (" . (int)$cat . ", " . (int)$postid . ")";
|
|
serendipity_db_query($query);
|
|
}
|
|
$cache = ContentCache::getInstance();
|
|
$cache->clearCache();
|
|
}
|
|
|
|
/**
|
|
* Gather an archive listing of older entries and passes it to Smarty
|
|
*
|
|
* The archives are created according to the current timestamp and show the current year.
|
|
* $serendipity['GET']['category'] is honoured like in serendipity_fetchEntries()
|
|
* $serendipity['GET']['viewAuthor'] is honoured like in serendipity_fetchEntries()
|
|
*
|
|
* @access public
|
|
* @return null
|
|
*/
|
|
function serendipity_printArchives() {
|
|
global $serendipity;
|
|
|
|
$f = serendipity_db_query("SELECT timestamp FROM {$serendipity['dbPrefix']}entries ORDER BY timestamp ASC LIMIT 1");
|
|
switch($serendipity['calendar']) {
|
|
case 'gregorian':
|
|
default:
|
|
$lastYear = date('Y', serendipity_serverOffsetHour($f[0][0]));
|
|
$lastMonth = date('m', serendipity_serverOffsetHour($f[0][0]));
|
|
$thisYear = date('Y', serendipity_serverOffsetHour());
|
|
$thisMonth = date('m', serendipity_serverOffsetHour());
|
|
break;
|
|
case 'persian-utf8':
|
|
require_once S9Y_INCLUDE_PATH . 'include/functions_calendars.inc.php';
|
|
$lastYear = persian_date_utf('Y', serendipity_serverOffsetHour($f[0][0]));
|
|
$lastMonth = persian_date_utf('m', serendipity_serverOffsetHour($f[0][0]));
|
|
$thisYear = persian_date_utf('Y', serendipity_serverOffsetHour());
|
|
$thisMonth = persian_date_utf('m', serendipity_serverOffsetHour());
|
|
break;
|
|
}
|
|
$max = 1;
|
|
|
|
if (isset($serendipity['GET']['category'])) {
|
|
$cat_sql = serendipity_getMultiCategoriesSQL($serendipity['GET']['category']);
|
|
$cat_get = '/C' . (int)$serendipity['GET']['category'];
|
|
} else {
|
|
$cat_sql = '';
|
|
$cat_get = '';
|
|
}
|
|
|
|
if (isset($serendipity['GET']['viewAuthor'])) {
|
|
$author_get = '/A' . (int)$serendipity['GET']['viewAuthor'];
|
|
} else {
|
|
$author_get = '';
|
|
}
|
|
|
|
if ($serendipity['dbType'] == 'postgres' ||
|
|
$serendipity['dbType'] == 'pdo-postgres') {
|
|
$distinct = 'DISTINCT e.id,';
|
|
} else {
|
|
$distinct = '';
|
|
}
|
|
|
|
// if another language than default is selected and hide option is set, hide untranslated articles
|
|
$sql_condition = array();
|
|
serendipity_plugin_api::hook_event('frontend_fetchentries', $sql_condition);
|
|
|
|
$q = "SELECT $distinct e.timestamp
|
|
FROM {$serendipity['dbPrefix']}entries e
|
|
" . (!empty($cat_sql) ? "
|
|
LEFT JOIN {$serendipity['dbPrefix']}entrycat ec
|
|
ON e.id = ec.entryid
|
|
LEFT JOIN {$serendipity['dbPrefix']}category c
|
|
ON ec.categoryid = c.categoryid" : "") .
|
|
($sql_condition['joins'] ?? '') .
|
|
" WHERE isdraft = 'false'"
|
|
. ($sql_condition['and'] ?? '')
|
|
. (!serendipity_db_bool($serendipity['showFutureEntries']) ? " AND timestamp <= " . serendipity_db_time() : '')
|
|
. (!empty($cat_sql) ? ' AND ' . $cat_sql : '')
|
|
. (!empty($serendipity['GET']['viewAuthor']) ? ' AND e.authorid = ' . (int)$serendipity['GET']['viewAuthor'] : '')
|
|
. (!empty($cat_sql) ? " GROUP BY e.id, e.timestamp" : '');
|
|
|
|
$entries =& serendipity_db_query($q, false, 'assoc');
|
|
|
|
$group = array();
|
|
if (is_array($entries)) {
|
|
foreach($entries AS $entry) {
|
|
$grouptime = date('Ym', $entry['timestamp']);
|
|
if (array_key_exists($grouptime, $group)) {
|
|
$group[$grouptime]++;
|
|
} else {
|
|
$group[$grouptime] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
$output = array();
|
|
for ($y = $thisYear; $y >= $lastYear; $y--) {
|
|
|
|
for ($m = 12; $m >= 1; $m--) {
|
|
|
|
/* If the month we are checking are in the future, we drop it */
|
|
if ($m > $thisMonth && $y == $thisYear) {
|
|
continue;
|
|
}
|
|
|
|
/* If the month is lower than the lowest month containing entries, we're done */
|
|
if ($m < $lastMonth && $y <= $lastYear) {
|
|
break;
|
|
}
|
|
|
|
switch($serendipity['calendar']) {
|
|
case 'gregorian':
|
|
default:
|
|
$s = serendipity_serverOffsetHour(mktime(0, 0, 0, $m, 1, $y), true);
|
|
$e = serendipity_serverOffsetHour(mktime(23, 59, 59, $m, date('t', $s), $y), true);
|
|
break;
|
|
case 'persian-utf8':
|
|
require_once S9Y_INCLUDE_PATH . 'include/functions_calendars.inc.php';
|
|
$s = serendipity_serverOffsetHour(persian_mktime(0, 0, 0, $m, 1, $y), true);
|
|
$e = serendipity_serverOffsetHour(persian_mktime(23, 59, 59, $m, date('t', $s), $y), true);
|
|
break;
|
|
}
|
|
|
|
$grouptime = $y . (strlen($m) == 1 ? '0' : '') . $m;
|
|
$entry_count = (int)($group[$grouptime] ?? 0);
|
|
|
|
if ($entry_count > 0) {
|
|
|
|
if (!array_key_exists($y,$output)) $output[$y]['year'] = $y;
|
|
|
|
/* A silly hack to get the maximum amount of entries per month */
|
|
if ($entry_count > $max) {
|
|
$max = $entry_count;
|
|
}
|
|
|
|
$data = array();
|
|
$data['entry_count'] = $entry_count;
|
|
$data['link'] = serendipity_archiveDateUrl($y . '/'. sprintf('%02s', $m) . $cat_get . $author_get);
|
|
$data['link_summary'] = serendipity_archiveDateUrl($y . '/'. sprintf('%02s', $m) . $cat_get . $author_get, true);
|
|
$data['date'] = $s;
|
|
$output[$y]['months'][] = $data;
|
|
}
|
|
}
|
|
}
|
|
|
|
$serendipity['smarty']->assignByRef('archives', $output);
|
|
$serendipity['smarty']->assignByRef('max_entries', $max);
|
|
|
|
serendipity_smarty_fetch('ARCHIVES', 'entries_archives.tpl', true);
|
|
}
|
|
|
|
/**
|
|
* Get total count for specific objects
|
|
*
|
|
* @access public
|
|
* @param string The type of count to show: "entries", "trackbacks", "comments"
|
|
* @return string The number
|
|
*/
|
|
function serendipity_getTotalCount($what) {
|
|
global $serendipity;
|
|
|
|
switch($what) {
|
|
case 'comments':
|
|
$res = serendipity_db_query("SELECT SUM(e.comments) AS sum
|
|
FROM {$serendipity['dbPrefix']}entries AS e
|
|
WHERE e.isdraft = 'false'
|
|
" . (!serendipity_db_bool($serendipity['showFutureEntries']) ? " AND e.timestamp <= " . serendipity_db_time() : ''), true, 'assoc');
|
|
return $res['sum'];
|
|
case 'trackbacks':
|
|
$res = serendipity_db_query("SELECT SUM(e.trackbacks) AS sum
|
|
FROM {$serendipity['dbPrefix']}entries AS e
|
|
WHERE e.isdraft = 'false'
|
|
" . (!serendipity_db_bool($serendipity['showFutureEntries']) ? " AND e.timestamp <= " . serendipity_db_time() : ''), true, 'assoc');
|
|
return $res['sum'];
|
|
case 'entries':
|
|
$res = serendipity_db_query("SELECT COUNT(e.id) AS sum
|
|
FROM {$serendipity['dbPrefix']}entries AS e
|
|
WHERE e.isdraft = 'false'
|
|
" . (!serendipity_db_bool($serendipity['showFutureEntries']) ? " AND e.timestamp <= " . serendipity_db_time() : ''), true, 'assoc');
|
|
return $res['sum'];
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a path of all parent categories to a given category.
|
|
*
|
|
* @access public
|
|
* @param string The id of the category, whose parents you want to fetch
|
|
* @return array An Array with all category information, ordered from root to the ID you supplied.
|
|
*/
|
|
function serendipity_getCategoryRoot($id) {
|
|
global $serendipity;
|
|
|
|
$r = serendipity_db_query("SELECT p.*
|
|
FROM {$serendipity['dbPrefix']}category n,
|
|
{$serendipity['dbPrefix']}category p
|
|
WHERE n.category_left BETWEEN p.category_left AND p.category_right
|
|
AND n.categoryid = " . (int)$id . "
|
|
ORDER BY n.category_left DESC, p.category_left ASC");
|
|
return $r;
|
|
}
|