1361 lines
46 KiB
PHP
1361 lines
46 KiB
PHP
<?php
|
|
# Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team)
|
|
# All rights reserved. See LICENSE file for licensing details
|
|
|
|
if (IN_serendipity !== true) {
|
|
die ("Don't hack!");
|
|
}
|
|
|
|
if (defined('S9Y_FRAMEWORK_FUNCTIONS')) {
|
|
return;
|
|
}
|
|
@define('S9Y_FRAMEWORK_FUNCTIONS', true);
|
|
|
|
$serendipity['imageList'] = array();
|
|
|
|
include_once(S9Y_INCLUDE_PATH . 'include/db/db.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/compat.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/functions_config.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/plugin_api.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/functions_images.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/functions_installer.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/functions_entries.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/functions_comments.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/functions_permalinks.inc.php');
|
|
include_once(S9Y_INCLUDE_PATH . 'include/functions_smarty.inc.php');
|
|
|
|
/**
|
|
* Truncate a string to a specific length, multibyte aware. Appends '...' if successfully truncated
|
|
*
|
|
* @access public
|
|
* @param string Input string
|
|
* @param int Length the final string should have
|
|
* @return string Truncated string
|
|
*/
|
|
function serendipity_truncateString($s, $len) {
|
|
if ( strlen($s) > ($len+3) ) {
|
|
$s = mb_substr($s, 0, $len) . '...';
|
|
}
|
|
return $s;
|
|
}
|
|
|
|
/**
|
|
* Optionally turn on GZip Compression, if configured
|
|
*
|
|
* @access public
|
|
*/
|
|
function serendipity_gzCompression() {
|
|
global $serendipity;
|
|
if (isset($serendipity['useGzip']) && serendipity_db_bool($serendipity['useGzip'])
|
|
&& function_exists('ob_gzhandler') && extension_loaded('zlib')
|
|
&& serendipity_ini_bool(ini_get('zlib.output_compression')) == false
|
|
&& serendipity_ini_bool(ini_get('session.use_trans_sid')) == false) {
|
|
ob_start("ob_gzhandler");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a timestamp formatted according to the current Server timezone offset
|
|
*
|
|
* @access public
|
|
* @param int The timestamp you want to convert into the current server timezone. Defaults to "now".
|
|
* @param boolean A toggle to indicate, if the timezone offset should be ADDED or SUBSTRACTED from the timezone. Substracting is required to restore original time when posting an entry.
|
|
* @return int The final timestamp
|
|
*/
|
|
function serendipity_serverOffsetHour($timestamp = null, $negative = false) {
|
|
global $serendipity;
|
|
|
|
if ($timestamp === null) {
|
|
$timestamp = time();
|
|
}
|
|
|
|
if (empty($serendipity['serverOffsetHours']) || !is_numeric($serendipity['serverOffsetHours']) || $serendipity['serverOffsetHours'] == 0) {
|
|
return $timestamp;
|
|
} else {
|
|
return $timestamp + (($negative ? -$serendipity['serverOffsetHours'] : $serendipity['serverOffsetHours']) * 60 * 60);
|
|
}
|
|
}
|
|
|
|
/* Converts a date string (DD.MM.YYYY, YYYY-MM-DD, MM/DD/YYYY) into a unix timestamp
|
|
*
|
|
* @access public
|
|
* @param string The input date
|
|
* @return int The output unix timestamp
|
|
*/
|
|
function &serendipity_convertToTimestamp($in) {
|
|
if (preg_match('@([0-9]+)([/\.-])([0-9]+)([/\.-])([0-9]+)@', $in, $m)) {
|
|
if ($m[2] != $m[4]) {
|
|
return $in;
|
|
}
|
|
|
|
switch($m[2]) {
|
|
case '.':
|
|
return mktime(0, 0, 0, /* month */ $m[3], /* day */ $m[1], /* year */ $m[5]);
|
|
break;
|
|
|
|
case '/':
|
|
return mktime(0, 0, 0, /* month */ $m[1], /* day */ $m[3], /* year */ $m[5]);
|
|
break;
|
|
|
|
case '-':
|
|
return mktime(0, 0, 0, /* month */ $m[3], /* day */ $m[5], /* year */ $m[1]);
|
|
break;
|
|
}
|
|
|
|
return $in;
|
|
}
|
|
|
|
return $in;
|
|
}
|
|
|
|
/**
|
|
* Format a timestamp
|
|
*
|
|
* This function can convert an input timestamp into specific PHP strftime() outputs, including applying necessary timezone calculations.
|
|
*
|
|
* @access public
|
|
* @param string Output format for the timestamp
|
|
* @param int Timestamp to use for displaying
|
|
* @param boolean Indicates, if timezone calculations shall be used.
|
|
* @param boolean Whether to use strftime or simply date
|
|
* @return string The formatted timestamp
|
|
*/
|
|
function serendipity_strftime($format, $timestamp = null, $useOffset = true, $useDate = false) {
|
|
global $serendipity;
|
|
static $is_win_utf = null;
|
|
|
|
if ($is_win_utf === null) {
|
|
// Windows does not have UTF-8 locales.
|
|
$is_win_utf = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? true : false);
|
|
}
|
|
|
|
if ($useDate) {
|
|
$out = date($format, $timestamp);
|
|
} else {
|
|
switch($serendipity['calendar']) {
|
|
default:
|
|
case 'gregorian':
|
|
if ($timestamp == null) {
|
|
$timestamp = serendipity_serverOffsetHour();
|
|
} elseif ($useOffset) {
|
|
$timestamp = serendipity_serverOffsetHour($timestamp);
|
|
}
|
|
$out = strftime($format, $timestamp);
|
|
break;
|
|
|
|
case 'persian-utf8':
|
|
if ($timestamp == null) {
|
|
$timestamp = serendipity_serverOffsetHour();
|
|
} elseif ($useOffset) {
|
|
$timestamp = serendipity_serverOffsetHour($timestamp);
|
|
}
|
|
|
|
require_once S9Y_INCLUDE_PATH . 'include/functions_calendars.inc.php';
|
|
$out = persian_strftime_utf($format, $timestamp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($is_win_utf && (empty($serendipity['calendar']) || $serendipity['calendar'] == 'gregorian')) {
|
|
$out = utf8_encode($out);
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* A wrapper function call for formatting Timestamps.
|
|
*
|
|
* Utilizes serendipity_strftime() and prepares the output timestamp with a few tweaks, and applies automatic uppercasing of the return.
|
|
*
|
|
* @see serendipity_strftime()
|
|
* @param string Output format for the timestamp
|
|
* @param int Timestamp to use for displaying
|
|
* @param boolean Indicates, if timezone calculations shall be used.
|
|
* @param boolean Whether to use strftime or simply date
|
|
* @return string The formatted timestamp
|
|
*/
|
|
function serendipity_formatTime($format, $time, $useOffset = true, $useDate = false) {
|
|
static $cache;
|
|
if (!isset($cache)) {
|
|
$cache = array();
|
|
}
|
|
|
|
if (!isset($cache[$format])) {
|
|
$cache[$format] = $format;
|
|
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
|
$cache[$format] = str_replace('%e', '%d', $cache[$format]);
|
|
}
|
|
}
|
|
|
|
return serendipity_mb_ucfirst(serendipity_strftime($cache[$format], (int)$time, $useOffset, $useDate));
|
|
}
|
|
|
|
/**
|
|
* Fetches the list of available templates/themes/styles.
|
|
*
|
|
* @access public
|
|
* @param string Directory to search for a template [recursive use]
|
|
* @return array Sorted array of available template names
|
|
*/
|
|
function serendipity_fetchTemplates($dir = '') {
|
|
global $serendipity;
|
|
|
|
$cdir = @opendir($serendipity['serendipityPath'] . $serendipity['templatePath'] . $dir);
|
|
$rv = array();
|
|
if (!$cdir) {
|
|
return $rv;
|
|
}
|
|
while (($file = readdir($cdir)) !== false) {
|
|
if (is_dir($serendipity['serendipityPath'] . $serendipity['templatePath'] . $dir . $file) && !preg_match('@^(\.|CVS)@i', $file) && !file_exists($serendipity['serendipityPath'] . $serendipity['templatePath'] . $dir . $file . '/inactive.txt')) {
|
|
if (file_exists($serendipity['serendipityPath'] . $serendipity['templatePath'] . $dir . $file . '/info.txt')) {
|
|
$key = strtolower($file);
|
|
if (isset($rv[$key])) {
|
|
$key = $dir . $key;
|
|
}
|
|
$rv[$key] = $dir . $file;
|
|
} else {
|
|
$temp = serendipity_fetchTemplates($dir . $file . '/');
|
|
if (count($temp) > 0) {
|
|
$rv = array_merge($rv, $temp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
closedir($cdir);
|
|
ksort($rv);
|
|
return $rv;
|
|
}
|
|
|
|
/**
|
|
* Get information about a specific theme/template/style
|
|
*
|
|
* @access public
|
|
* @param string Directory name of a theme
|
|
* @param string Absolute path to the templates [for use on CVS mounted directories]
|
|
* @return array Associative array if template information
|
|
*/
|
|
function serendipity_fetchTemplateInfo($theme, $abspath = null) {
|
|
global $serendipity;
|
|
|
|
if ($abspath === null) {
|
|
$abspath = $serendipity['serendipityPath'] . $serendipity['templatePath'];
|
|
}
|
|
|
|
$lines = @file($abspath . $theme . '/info.txt');
|
|
if ( !$lines ) {
|
|
return array();
|
|
}
|
|
|
|
for($x=0; $x<count($lines); $x++) {
|
|
$j = preg_split('/([^\:]+)\:/', $lines[$x], -1, PREG_SPLIT_DELIM_CAPTURE);
|
|
if ($j[2]) {
|
|
$currSec = $j[1];
|
|
$data[strtolower($currSec)][] = trim($j[2]);
|
|
} else {
|
|
$data[strtolower($currSec)][] = trim($j[0]);
|
|
}
|
|
}
|
|
|
|
foreach ($data as $k => $v) {
|
|
$data[$k] = implode("\n", $v);
|
|
}
|
|
|
|
if (@is_file($serendipity['templatePath'] . $theme . '/config.inc.php')) {
|
|
$data['custom_config'] = YES;
|
|
$data['custom_config_engine'] = $theme;
|
|
} else {
|
|
$data['custom_config'] = NO;
|
|
}
|
|
|
|
// Templates can depend on a possible "Engine" (i.e. "Engine: 2k11").
|
|
// We support the fallback chain also of a template's configuration, so let's check each engine for a config file.
|
|
if (!empty($data['engine'])) {
|
|
$engines = explode(',', $data['engine']);
|
|
foreach($engines AS $engine) {
|
|
$engine = trim($engine);
|
|
if (empty($engine)) continue;
|
|
|
|
if (@is_file($serendipity['templatePath'] . $engine . '/config.inc.php')) {
|
|
$data['custom_config'] = YES;
|
|
$data['custom_config_engine'] = $engine;
|
|
} else {
|
|
$data['custom_config'] = NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $theme != 'default' && $theme != 'default-rtl'
|
|
&& @is_dir($serendipity['templatePath'] . $theme . '/admin')
|
|
&& strtolower($data['backend'] ?? '') == 'yes' ) {
|
|
|
|
$data['custom_admin_interface'] = YES;
|
|
} else {
|
|
$data['custom_admin_interface'] = NO;
|
|
}
|
|
|
|
# php 8 compat section
|
|
if (! isset($data['customURI'])) { $data['customURI'] = null; }
|
|
if (! isset($data['author'])) { $data['author'] = null; }
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Recursively walks an 1-dimensional array to map parent IDs and depths, depending on the nested array set.
|
|
*
|
|
* Used for sorting a list of comments, for example. The list of comment is iterated, and the nesting level is calculated, and the array will be sorted to represent the amount of nesting.
|
|
*
|
|
* @access public
|
|
* @param array Input array to investigate [consecutively sliced for recursive calls]
|
|
* @param string Array index name to indicate the ID value of an array index
|
|
* @param string Array index name to indicate the PARENT ID value of an array index, matched against the $child_name value
|
|
* @param int The parent id to check an element against for recursive nesting
|
|
* @param int The current depth of the cycled array
|
|
* @return array The sorted and shiny polished result array
|
|
*/
|
|
function serendipity_walkRecursive($ary, $child_name = 'id', $parent_name = 'parent_id', $parentid = 0, $depth = 0) {
|
|
global $serendipity;
|
|
static $_resArray;
|
|
static $_remain;
|
|
|
|
if (!is_array($ary) || sizeof($ary) == 0) {
|
|
return array();
|
|
}
|
|
|
|
if ($parentid === VIEWMODE_THREADED) {
|
|
$parentid = 0;
|
|
}
|
|
|
|
if ($depth == 0) {
|
|
$_resArray = array();
|
|
$_remain = $ary;
|
|
}
|
|
|
|
foreach($ary AS $key => $data) {
|
|
if ($parentid === VIEWMODE_LINEAR || !isset($data[$parent_name]) || $data[$parent_name] == $parentid) {
|
|
$data['depth'] = $depth;
|
|
$_resArray[] = $data;
|
|
unset($_remain[$key]);
|
|
if ($data[$child_name] && $parentid !== VIEWMODE_LINEAR ) {
|
|
serendipity_walkRecursive($ary, $child_name, $parent_name, $data[$child_name], ($depth+1));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We are inside a recusive child, and we need to break out */
|
|
if ($depth !== 0) {
|
|
return true;
|
|
}
|
|
|
|
if (count($_remain) > 0) {
|
|
// Remaining items need to be appended
|
|
foreach($_remain AS $key => $data) {
|
|
$data['depth'] = 0;
|
|
$_resArray[] = $data;
|
|
}
|
|
}
|
|
|
|
return $_resArray;
|
|
}
|
|
|
|
/**
|
|
* Fetch the list of Serendipity Authors
|
|
*
|
|
* @access public
|
|
* @param int Fetch only a specific User
|
|
* @param array Can contain an array of group IDs you only want to fetch authors of.
|
|
* @param boolean If set to TRUE, the amount of entries per author will also be returned
|
|
* @return array Result of the SQL query
|
|
*/
|
|
function serendipity_fetchUsers($user = '', $group = null, $is_count = false) {
|
|
global $serendipity;
|
|
|
|
$where = 'WHERE 1=1';
|
|
if (!empty($user)) {
|
|
$where .= " AND a.authorid = '" . (int)$user ."'";
|
|
}
|
|
|
|
$query_select = '';
|
|
$query_join = '';
|
|
$query_group = '';
|
|
$query_distinct = '';
|
|
if ($is_count) {
|
|
$query_select = ", count(e.authorid) as artcount";
|
|
$query_join = "LEFT OUTER JOIN {$serendipity['dbPrefix']}entries AS e
|
|
ON (a.authorid = e.authorid AND e.isdraft = 'false')";
|
|
}
|
|
|
|
if ($is_count || $group != null) {
|
|
if ($serendipity['dbType'] == 'postgres' ||
|
|
$serendipity['dbType'] == 'pdo-postgres') {
|
|
// Why does PostgreSQL keep doing this to us? :-)
|
|
$query_group = 'GROUP BY a.authorid, a.realname, a.username, a.password, a.hashtype, a.mail_comments, a.mail_trackbacks, a.email, a.userlevel, a.right_publish';
|
|
$query_distinct = 'DISTINCT';
|
|
} else {
|
|
$query_group = 'GROUP BY a.authorid';
|
|
$query_distinct = '';
|
|
}
|
|
}
|
|
|
|
|
|
if ($group === null) {
|
|
$querystring = "SELECT $query_distinct
|
|
a.authorid,
|
|
a.realname,
|
|
a.username,
|
|
a.password,
|
|
a.hashtype,
|
|
a.mail_comments,
|
|
a.mail_trackbacks,
|
|
a.email,
|
|
a.userlevel,
|
|
a.right_publish
|
|
$query_select
|
|
FROM {$serendipity['dbPrefix']}authors AS a
|
|
$query_join
|
|
$where
|
|
$query_group
|
|
ORDER BY a.realname ASC";
|
|
} else {
|
|
|
|
if ($group === 'hidden') {
|
|
$query_join .= "LEFT OUTER JOIN {$serendipity['dbPrefix']}groupconfig AS gc
|
|
ON (gc.property = 'hiddenGroup' AND gc.id = ag.groupid AND gc.value = 'true')";
|
|
$where .= ' AND gc.id IS NULL';
|
|
} elseif (is_array($group)) {
|
|
foreach($group AS $idx => $groupid) {
|
|
$group[$idx] = (int)$groupid;
|
|
}
|
|
$group_sql = implode(', ', $group);
|
|
} else {
|
|
$group_sql = (int)$group;
|
|
}
|
|
|
|
if ($group_sql ?? false) {
|
|
$where .= ' AND ' . "g.id IN ($group_sql)";
|
|
}
|
|
|
|
$querystring = "SELECT $query_distinct
|
|
a.authorid,
|
|
a.realname,
|
|
a.username,
|
|
a.password,
|
|
a.hashtype,
|
|
a.mail_comments,
|
|
a.mail_trackbacks,
|
|
a.email,
|
|
a.userlevel,
|
|
a.right_publish
|
|
$query_select
|
|
FROM {$serendipity['dbPrefix']}authors AS a
|
|
LEFT OUTER JOIN {$serendipity['dbPrefix']}authorgroups AS ag
|
|
ON a.authorid = ag.authorid
|
|
LEFT OUTER JOIN {$serendipity['dbPrefix']}groups AS g
|
|
ON ag.groupid = g.id
|
|
$query_join
|
|
$where
|
|
$query_group
|
|
ORDER BY a.realname ASC";
|
|
}
|
|
|
|
return serendipity_db_query($querystring);
|
|
}
|
|
|
|
|
|
/**
|
|
* Sends a Mail with Serendipity formatting
|
|
*
|
|
* @access public
|
|
* @param string The recipient address of the mail
|
|
* @param string The subject of the mail
|
|
* @param string The body of the mail
|
|
* @param string The sender mail address of the mail
|
|
* @param array additional headers to pass to the E-Mail
|
|
* @param string The name of the sender
|
|
* @return int Return code of the PHP mail() function
|
|
*/
|
|
function serendipity_sendMail($to, $subject, $message, $fromMail, $headers = NULL, $fromName = NULL) {
|
|
global $serendipity;
|
|
|
|
if (!is_null($headers) && !is_array($headers)) {
|
|
trigger_error(__FUNCTION__ . ': $headers must be either an array or null', E_USER_ERROR);
|
|
}
|
|
|
|
if (is_null($fromName) || empty($fromName)) {
|
|
$fromName = $serendipity['blogTitle'];
|
|
}
|
|
|
|
if (is_null($fromMail) || empty($fromMail)) {
|
|
$fromMail = $to;
|
|
}
|
|
|
|
if (is_null($headers)) {
|
|
$headers = array();
|
|
}
|
|
|
|
// Fix special characters
|
|
$fromName = str_replace(array('"', "\r", "\n"), array("'", '', ''), $fromName);
|
|
$fromMail = str_replace(array("\r","\n"), array('', ''), $fromMail);
|
|
|
|
// Prefix all mail with weblog title
|
|
$subject = '['. $serendipity['blogTitle'] . '] '. $subject;
|
|
|
|
// Append signature to every mail
|
|
$message .= "\n" . sprintf(SIGNATURE, $serendipity['blogTitle']);
|
|
|
|
$maildata = array(
|
|
'to' => &$to,
|
|
'subject' => &$subject,
|
|
'fromName' => &$fromName,
|
|
'fromMail' => &$fromMail,
|
|
'blogMail' => $serendipity['blogMail'],
|
|
'version' => 'Serendipity' . ($serendipity['expose_s9y'] ? '/' . $serendipity['version'] : ''),
|
|
'legacy' => true,
|
|
'headers' => &$headers,
|
|
'message' => &$message
|
|
);
|
|
|
|
serendipity_plugin_api::hook_event('backend_sendmail', $maildata, 'UTF-8');
|
|
|
|
// This routine can be overridden by a plugin.
|
|
if ($maildata['legacy']) {
|
|
// Check for mb_* function, and use it to encode headers etc. */
|
|
if (function_exists('mb_encode_mimeheader')) {
|
|
// mb_encode_mimeheader function insertes linebreaks after 74 chars.
|
|
// Usually this is according to spec, but for us it caused more trouble than
|
|
// it prevented.
|
|
// Regards to Mark Kronsbein for finding this issue!
|
|
$maildata['subject'] = str_replace(array("\n", "\r"), array('', ''), mb_encode_mimeheader($maildata['subject'], 'UTF-8'));
|
|
$maildata['fromName'] = str_replace(array("\n", "\r"), array('', ''), mb_encode_mimeheader($maildata['fromName'], 'UTF-8'));
|
|
}
|
|
|
|
|
|
// Always add these headers
|
|
if (!empty($maildata['blogMail'])) {
|
|
$maildata['headers'][] = 'From: "'. $maildata['fromName'] .'" <'. $maildata['blogMail'] .'>';
|
|
}
|
|
$maildata['headers'][] = 'Reply-To: "'. $maildata['fromName'] .'" <'. $maildata['fromMail'] .'>';
|
|
if ($serendipity['expose_s9y']) {
|
|
$maildata['headers'][] = 'X-Mailer: ' . $maildata['version'];
|
|
$maildata['headers'][] = 'X-Engine: PHP/'. phpversion();
|
|
}
|
|
$maildata['headers'][] = 'Message-ID: <'. bin2hex(random_bytes(16)) .'@'. $_SERVER['HTTP_HOST'] .'>';
|
|
$maildata['headers'][] = 'MIME-Version: 1.0';
|
|
$maildata['headers'][] = 'Precedence: bulk';
|
|
$maildata['headers'][] = 'Content-Type: text/plain; charset=utf-8';
|
|
$maildata['headers'][] = 'Auto-Submitted: auto-generated';
|
|
|
|
if (function_exists('imap_8bit') && !$serendipity['forceBase64']) {
|
|
$maildata['headers'][] = 'Content-Transfer-Encoding: quoted-printable';
|
|
$maildata['message'] = str_replace("\r\n","\n",imap_8bit($maildata['message']));
|
|
} else {
|
|
$maildata['headers'][] = 'Content-Transfer-Encoding: base64';
|
|
$maildata['message'] = chunk_split(base64_encode($maildata['message']));
|
|
}
|
|
}
|
|
|
|
if ($serendipity['dumpMail'] ?? false) {
|
|
$fp = fopen($serendipity['serendipityPath'] . '/templates_c/mail.log', 'a');
|
|
fwrite($fp, date('Y-m-d H:i') . "\n" . print_r($maildata, true));
|
|
fclose($fp);
|
|
}
|
|
|
|
if (!isset($maildata['skip_native']) && !empty($maildata['to'])) {
|
|
return mail($maildata['to'], $maildata['subject'], $maildata['message'], implode("\n", $maildata['headers']));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch all references (links) from a given entry ID
|
|
*
|
|
* @access public
|
|
* @param int The entry ID
|
|
* @return array The SQL result containing the references/links of an entry
|
|
*/
|
|
function serendipity_fetchReferences($id) {
|
|
global $serendipity;
|
|
|
|
$query = "SELECT name,link FROM {$serendipity['dbPrefix']}references WHERE entry_id = '" . (int)$id . "' AND type = ''";
|
|
|
|
return serendipity_db_query($query);
|
|
}
|
|
|
|
|
|
/**
|
|
* Encode a string to UTF-8, if not already in UTF-8 format.
|
|
*
|
|
* @access public
|
|
* @param string The input string
|
|
* @return string The output string
|
|
*/
|
|
function serendipity_utf8_encode($string) {
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Create a link that can be used within a RSS feed to indicate a permalink for an entry or comment
|
|
*
|
|
* @access public
|
|
* @param array The input entry array
|
|
* @param boolean Toggle whether the link will be for a COMMENT [true] or an ENTRY [false]
|
|
* @return string A permalink for the given entry
|
|
*/
|
|
function serendipity_rss_getguid($entry, $comments = false) {
|
|
global $serendipity;
|
|
|
|
$id = (isset($entry['entryid']) && $entry['entryid'] != '' ? $entry['entryid'] : $entry['id']);
|
|
|
|
// When using %id%, we can make the GUID shorter and independent from the title.
|
|
// If not using %id%, the entryid needs to be used for uniqueness.
|
|
if (stristr($serendipity['permalinkStructure'], '%id%') !== FALSE) {
|
|
$title = 'guid';
|
|
} else {
|
|
$title = $id;
|
|
}
|
|
|
|
$guid = serendipity_archiveURL(
|
|
$id,
|
|
$title,
|
|
'baseURL',
|
|
true,
|
|
array('timestamp' => $entry['timestamp'])
|
|
);
|
|
|
|
if ($comments == true) {
|
|
$guid .= '#c' . $entry['commentid'];
|
|
}
|
|
|
|
return $guid;
|
|
}
|
|
|
|
/**
|
|
* Perform some replacement calls to make valid XHTML content
|
|
*
|
|
* jbalcorn: starter function to clean up xhtml for atom feed. Add things to this as we find common
|
|
* mistakes, unless someone finds a better way to do this.
|
|
* DONE:
|
|
* since someone encoded all the urls, we can now assume any amp followed by
|
|
* whitespace or a HTML tag (i.e. &<br /> )should be
|
|
* encoded and most not with a space are intentional
|
|
*
|
|
* @access public
|
|
* @param string Input HTML code
|
|
* @return string Cleaned HTML code
|
|
*/
|
|
function xhtml_cleanup($html) {
|
|
static $p = array(
|
|
'/\&([\s\<])/', // ampersand followed by whitespace or tag
|
|
'/\&$/', // ampersand at end of body
|
|
'/<(br|hr|img)([^\/>]*)>/i', // unclosed br tag - attributes included
|
|
'/\ /' // Protect whitespace
|
|
);
|
|
|
|
static $r = array(
|
|
'&\1',
|
|
'&',
|
|
'<\1\2 />',
|
|
' '
|
|
);
|
|
|
|
return preg_replace($p, $r, $html);
|
|
}
|
|
|
|
/**
|
|
* Fetch user data for a specific Serendipity author
|
|
*
|
|
* @access public
|
|
* @param int The requested author id
|
|
* @return array The SQL result array
|
|
*/
|
|
function serendipity_fetchAuthor($author) {
|
|
global $serendipity;
|
|
|
|
return serendipity_db_query("SELECT * FROM {$serendipity['dbPrefix']}authors WHERE " . (is_numeric($author) ? "authorid={$author};" : "username='" . serendipity_db_escape_string($author) . "';"));
|
|
}
|
|
|
|
/**
|
|
* Split a filename into basename and extension parts
|
|
*
|
|
* @access public
|
|
* @param string Filename
|
|
* @return array Return array containing the basename and file extension
|
|
*/
|
|
function serendipity_parseFileName($file) {
|
|
$x = explode('.', $file);
|
|
if (count($x)>1){
|
|
$suf = array_pop($x);
|
|
$f = @implode('.', $x);
|
|
return array($f, $suf);
|
|
}
|
|
else {
|
|
return array($file,'');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Track the referer to a specific Entry ID
|
|
*
|
|
* @access public
|
|
* @param int Entry ID
|
|
* @return null
|
|
*/
|
|
function serendipity_track_referrer($entry = 0) {
|
|
global $serendipity;
|
|
|
|
// Tracking disabled.
|
|
if ($serendipity['trackReferrer'] === false) {
|
|
return;
|
|
}
|
|
|
|
if (isset($_SERVER['HTTP_REFERER'])) {
|
|
if (stristr($_SERVER['HTTP_REFERER'], $serendipity['baseURL']) !== false) {
|
|
return;
|
|
}
|
|
|
|
if (!isset($serendipity['_blockReferer']) || !is_array($serendipity['_blockReferer'])) {
|
|
// Only generate an array once per call
|
|
$serendipity['_blockReferer'] = array();
|
|
$serendipity['_blockReferer'] = @explode(';', $serendipity['blockReferer']);
|
|
}
|
|
|
|
$url_parts = parse_url($_SERVER['HTTP_REFERER']);
|
|
$host_parts = explode('.', $url_parts['host']);
|
|
if (!$url_parts['host'] ||
|
|
strstr($url_parts['host'], $_SERVER['SERVER_NAME'])) {
|
|
return;
|
|
}
|
|
|
|
foreach($serendipity['_blockReferer'] AS $idx => $hostname) {
|
|
if (@strstr($url_parts['host'], $hostname)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (rand(0, 100) < 1) {
|
|
serendipity_track_referrer_gc();
|
|
}
|
|
|
|
$ts = serendipity_db_get_interval('ts');
|
|
$interval = serendipity_db_get_interval('interval', 900);
|
|
|
|
$url_parts['query'] = substr($url_parts['query'], 0, 255);
|
|
|
|
$suppressq = "SELECT count(1)
|
|
FROM $serendipity[dbPrefix]suppress
|
|
WHERE ip = '" . serendipity_db_escape_string($_SERVER['REMOTE_ADDR']) . "'
|
|
AND scheme = '" . serendipity_db_escape_string($url_parts['scheme']) . "'
|
|
AND port = '" . serendipity_db_escape_string($url_parts['port']) . "'
|
|
AND host = '" . serendipity_db_escape_string($url_parts['host']) . "'
|
|
AND path = '" . serendipity_db_escape_string($url_parts['path']) . "'
|
|
AND query = '" . serendipity_db_escape_string($url_parts['query']) . "'
|
|
AND last > $ts - $interval";
|
|
|
|
$suppressp = "DELETE FROM $serendipity[dbPrefix]suppress
|
|
WHERE ip = '" . serendipity_db_escape_string($_SERVER['REMOTE_ADDR']) . "'
|
|
AND scheme = '" . serendipity_db_escape_string($url_parts['scheme']) . "'
|
|
AND host = '" . serendipity_db_escape_string($url_parts['host']) . "'
|
|
AND port = '" . serendipity_db_escape_string($url_parts['port']) . "'
|
|
AND query = '" . serendipity_db_escape_string($url_parts['query']) . "'
|
|
AND path = '" . serendipity_db_escape_string($url_parts['path']) . "'";
|
|
$suppressu = "INSERT INTO $serendipity[dbPrefix]suppress
|
|
(ip, last, scheme, host, port, path, query)
|
|
VALUES (
|
|
'" . serendipity_db_escape_string($_SERVER['REMOTE_ADDR']) . "',
|
|
$ts,
|
|
'" . serendipity_db_escape_string($url_parts['scheme']) . "',
|
|
'" . serendipity_db_escape_string($url_parts['host']) . "',
|
|
'" . serendipity_db_escape_string($url_parts['port']) . "',
|
|
'" . serendipity_db_escape_string($url_parts['path']) . "',
|
|
'" . serendipity_db_escape_string($url_parts['query']) . "'
|
|
)";
|
|
|
|
$count = serendipity_db_query($suppressq, true);
|
|
|
|
if ($count[0] == 0) {
|
|
serendipity_db_query($suppressu);
|
|
return;
|
|
}
|
|
|
|
serendipity_db_query($suppressp);
|
|
serendipity_db_query($suppressu);
|
|
|
|
serendipity_track_url('referrers', $_SERVER['HTTP_REFERER'], $entry);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Garbage Collection for suppressed referrers
|
|
*
|
|
* "Bad" referrers, that only occurred once to your entry are put within a
|
|
* SUPPRESS database table. Entries contained there will be cleaned up eventually.
|
|
*
|
|
* @access public
|
|
* @return null
|
|
*/
|
|
function serendipity_track_referrer_gc() {
|
|
global $serendipity;
|
|
|
|
$ts = serendipity_db_get_interval('ts');
|
|
$interval = serendipity_db_get_interval('interval', 900);
|
|
$gc = "DELETE FROM $serendipity[dbPrefix]suppress WHERE last <= $ts - $interval";
|
|
serendipity_db_query($gc);
|
|
}
|
|
|
|
/**
|
|
* Track a URL used in your Blog (Exit-Tracking)
|
|
*
|
|
* @access public
|
|
* @param string Name of the DB table where to store the link (exits|referrers)
|
|
* @param string The URL to track
|
|
* @param int The Entry ID to relate the track to
|
|
* @return null
|
|
*/
|
|
function serendipity_track_url($list, $url, $entry_id = 0) {
|
|
global $serendipity;
|
|
|
|
$url_parts = parse_url($url);
|
|
$url_parts['query'] = substr($url_parts['query'], 0, 255);
|
|
|
|
serendipity_db_query(
|
|
@sprintf(
|
|
"UPDATE %s%s
|
|
SET count = count + 1
|
|
WHERE scheme = '%s'
|
|
AND host = '%s'
|
|
AND port = '%s'
|
|
AND path = '%s'
|
|
AND query = '%s'
|
|
AND day = '%s'
|
|
%s",
|
|
|
|
$serendipity['dbPrefix'],
|
|
$list,
|
|
serendipity_db_escape_string($url_parts['scheme']),
|
|
serendipity_db_escape_string($url_parts['host']),
|
|
serendipity_db_escape_string($url_parts['port']),
|
|
serendipity_db_escape_string($url_parts['path']),
|
|
serendipity_db_escape_string($url_parts['query']),
|
|
date('Y-m-d'),
|
|
($entry_id != 0) ? "AND entry_id = '". (int)$entry_id ."'" : ''
|
|
)
|
|
);
|
|
|
|
if (serendipity_db_affected_rows() == 0) {
|
|
serendipity_db_query(
|
|
sprintf(
|
|
"INSERT INTO %s%s
|
|
(entry_id, day, count, scheme, host, port, path, query)
|
|
VALUES (%d, '%s', 1, '%s', '%s', '%s', '%s', '%s')",
|
|
|
|
$serendipity['dbPrefix'],
|
|
$list,
|
|
(int)$entry_id,
|
|
date('Y-m-d'),
|
|
serendipity_db_escape_string($url_parts['scheme']),
|
|
serendipity_db_escape_string($url_parts['host']),
|
|
serendipity_db_escape_string($url_parts['port']),
|
|
serendipity_db_escape_string($url_parts['path']),
|
|
serendipity_db_escape_string($url_parts['query'])
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the list of top referrers
|
|
*
|
|
* @access public
|
|
* @see serendipity_displayTopUrlList()
|
|
* @param int Number of referrers to show
|
|
* @param boolean Whether to use HTML links for URLs
|
|
* @param int Interval for which the top referrers are aggregated
|
|
* @return string List of Top referrers
|
|
*/
|
|
function serendipity_displayTopReferrers($limit = 10, $use_links = true, $interval = 7) {
|
|
return serendipity_displayTopUrlList('referrers', $limit, $use_links, $interval);
|
|
}
|
|
|
|
/**
|
|
* Display the list of top exits
|
|
*
|
|
* @access public
|
|
* @see serendipity_displayTopUrlList()
|
|
* @param int Number of exits to show
|
|
* @param boolean Whether to use HTML links for URLs
|
|
* @param int Interval for which the top exits are aggregated
|
|
* @return string List of Top exits
|
|
*/
|
|
function serendipity_displayTopExits($limit = 10, $use_links = true, $interval = 7) {
|
|
return serendipity_displayTopUrlList('exits', $limit, $use_links, $interval);
|
|
}
|
|
|
|
/**
|
|
* Display HTML output data of a Exit/Referrer list
|
|
*
|
|
* @access public
|
|
* @see serendipity_displayTopExits()
|
|
* @see serendipity_displayTopReferrers()
|
|
* @param string Name of the DB table to show data from (exits|referrers)
|
|
* @param boolean Whether to use HTML links for URLs
|
|
* @param int Interval for which the top exits are aggregated
|
|
* @return
|
|
*/
|
|
function serendipity_displayTopUrlList($list, $limit, $use_links = true, $interval = 7) {
|
|
global $serendipity;
|
|
|
|
if ($limit){
|
|
$limit = serendipity_db_limit_sql($limit);
|
|
}
|
|
|
|
/* HACK */
|
|
if (preg_match('/^mysqli?/', $serendipity['dbType'])) {
|
|
/* Nonportable SQL due to MySQL date functions,
|
|
* but produces rolling 7 day totals, which is more
|
|
* interesting
|
|
*/
|
|
$query = "SELECT scheme, host, SUM(count) AS total
|
|
FROM {$serendipity['dbPrefix']}$list
|
|
WHERE day > date_sub(current_date, interval " . (int)$interval . " day)
|
|
GROUP BY host
|
|
ORDER BY total DESC, host
|
|
$limit";
|
|
} else {
|
|
/* Portable version of the same query */
|
|
$query = "SELECT scheme, host, SUM(count) AS total
|
|
FROM {$serendipity['dbPrefix']}$list
|
|
GROUP BY scheme, host
|
|
ORDER BY total DESC, host
|
|
$limit";
|
|
}
|
|
|
|
$rows = serendipity_db_query($query);
|
|
$output = "<span class='serendipityReferer'>";
|
|
if (is_array($rows)) {
|
|
foreach ($rows as $row) {
|
|
if ($use_links) {
|
|
$output .= sprintf(
|
|
'<span class="block_level"><a href="%1$s://%2$s" title="%2$s" >%2$s</a> (%3$s) </span>',
|
|
serendipity_specialchars($row['scheme']),
|
|
serendipity_specialchars($row['host']),
|
|
serendipity_specialchars($row['total'])
|
|
);
|
|
} else {
|
|
$output .= sprintf(
|
|
'<span class="block_level">%1$s (%2$s) </span>',
|
|
serendipity_specialchars($row['host']),
|
|
serendipity_specialchars($row['total'])
|
|
);
|
|
}
|
|
}
|
|
}
|
|
$output .= "</span>";
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Return either HTML or XHTML code for an '<a target...> attribute.
|
|
*
|
|
* @access public
|
|
* @param string The target to use (_blank, _parent, ...)
|
|
* @return string HTML string containig the valid markup for the target attribute.
|
|
*/
|
|
function serendipity_xhtml_target($target) {
|
|
global $serendipity;
|
|
|
|
if ($serendipity['enablePopup'] != true)
|
|
return "";
|
|
|
|
return ' onclick="window.open(this.href, \'target' . time() . '\'); return false;" ';
|
|
}
|
|
|
|
/**
|
|
* Parse a URI portion to return which RSS Feed version was requested
|
|
*
|
|
* @access public
|
|
* @param string Name of the core URI part
|
|
* @param string File extension name of the URI
|
|
* @return string RSS feed type/version
|
|
*/
|
|
function serendipity_discover_rss($name, $ext) {
|
|
static $default = '2.0';
|
|
|
|
/* Detect type */
|
|
if ($name == 'comments') {
|
|
$type = 'comments';
|
|
} elseif ($name == 'comments_and_trackbacks') {
|
|
$type = 'comments_and_trackbacks';
|
|
} elseif ($name == 'trackbacks') {
|
|
$type = 'trackbacks';
|
|
} else {
|
|
$type = 'content';
|
|
}
|
|
|
|
/* Detect version */
|
|
if ($name == 'atom' || $name == 'atom10' || $ext == 'atom') {
|
|
$ver = 'atom1.0';
|
|
} elseif ($name == 'atom03') {
|
|
$ver = 'atom0.3';
|
|
} elseif ($name == 'opml' || $ext == 'opml') {
|
|
$ver = 'opml1.0';
|
|
} elseif ($ext == 'rss') {
|
|
$ver = '0.91';
|
|
} elseif ($ext == 'rss1') {
|
|
$ver = '1.0';
|
|
} else {
|
|
$ver = $default;
|
|
}
|
|
|
|
return array($ver, $type);
|
|
}
|
|
|
|
/**
|
|
* Check whether an input string contains "evil" characters used for HTTP Response Splitting
|
|
*
|
|
* @access public
|
|
* @param string String to check for evil characters
|
|
* @return boolean Return true on success, false on failure
|
|
*/
|
|
function serendipity_isResponseClean($d) {
|
|
return (strpos($d, "\r") === false && strpos($d, "\n") === false && stripos($d, "%0A") === false && stripos($d, "%0D") === false);
|
|
}
|
|
|
|
/**
|
|
* Create a new Category
|
|
*
|
|
* @access public
|
|
* @param string The new category name
|
|
* @param string The new category description
|
|
* @param int The category owner
|
|
* @param string An icon representing the category
|
|
* @param int A possible parentid to a category
|
|
* @return int The new category's ID
|
|
*/
|
|
function serendipity_addCategory($name, $desc, $authorid, $icon, $parentid) {
|
|
global $serendipity;
|
|
$query = "INSERT INTO {$serendipity['dbPrefix']}category
|
|
(category_name, category_description, authorid, category_icon, parentid, category_left, category_right)
|
|
VALUES
|
|
('". serendipity_db_escape_string($name) ."',
|
|
'". serendipity_db_escape_string($desc) ."',
|
|
". (int)$authorid .",
|
|
'". serendipity_db_escape_string($icon) ."',
|
|
". (int)$parentid .",
|
|
0,
|
|
0)";
|
|
|
|
serendipity_db_query($query);
|
|
$cid = serendipity_db_insert_id('category', 'categoryid');
|
|
serendipity_plugin_api::hook_event('backend_category_addNew', $cid);
|
|
|
|
$data = array(
|
|
'categoryid' => $cid,
|
|
'category_name' => $name,
|
|
'category_description' => $desc
|
|
);
|
|
|
|
serendipity_plugin_api::hook_event('multilingual_strip_langs',$data, array('category_name','category_description'));
|
|
serendipity_insertPermalink($data, 'category');
|
|
return $cid;
|
|
}
|
|
|
|
/**
|
|
* Update an existing category
|
|
*
|
|
* @access public
|
|
* @param int Category ID to update
|
|
* @param string The new category name
|
|
* @param string The new category description
|
|
* @param int The new category owner
|
|
* @param string The new category icon
|
|
* @param int The new category parent ID
|
|
* @param int The new category sort order
|
|
* @param int The new category subcat hiding
|
|
* @return null
|
|
*/
|
|
function serendipity_updateCategory($cid, $name, $desc, $authorid, $icon, $parentid, $sort_order = 0, $hide_sub = 0, $admin_category = '') {
|
|
global $serendipity;
|
|
|
|
$query = "UPDATE {$serendipity['dbPrefix']}category
|
|
SET category_name = '". serendipity_db_escape_string($name) ."',
|
|
category_description = '". serendipity_db_escape_string($desc) ."',
|
|
authorid = ". (int)$authorid .",
|
|
category_icon = '". serendipity_db_escape_string($icon) ."',
|
|
parentid = ". (int)$parentid .",
|
|
sort_order = ". (int)$sort_order . ",
|
|
hide_sub = ". (int)$hide_sub . "
|
|
WHERE categoryid = ". (int)$cid ."
|
|
$admin_category";
|
|
serendipity_db_query($query);
|
|
serendipity_plugin_api::hook_event('backend_category_update', $cid);
|
|
|
|
$data = array(
|
|
'id' => $cid,
|
|
'categoryid' => $cid,
|
|
'category_name' => $name,
|
|
'category_description' => $desc
|
|
);
|
|
|
|
serendipity_plugin_api::hook_event('multilingual_strip_langs',$data, array('category_name','category_description'));
|
|
serendipity_updatePermalink($data, 'category');
|
|
}
|
|
|
|
/**
|
|
* Ends a session, so that while a file requests happens, Serendipity can work on in that session
|
|
*/
|
|
function serendipity_request_start() {
|
|
@session_write_close();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Continues a session after a file request
|
|
*/
|
|
function serendipity_request_end() {
|
|
@session_start();
|
|
return true;
|
|
}
|
|
|
|
/* Request the contents of an URL, API wrapper
|
|
* @param $uri string The URL to fetch
|
|
* @param $method string HTTP method (GET/POST/PUT/OPTIONS...)
|
|
* @param $contenttype string optional HTTP content type
|
|
* @param $contenttype mixed optional extra data (i.e. POST body), can be an array
|
|
* @param $extra_options array Extra options for HTTP_Request $options array (can override defaults)
|
|
* @param $addData string possible extra event addData declaration for 'backend_http_request' hook
|
|
* @param $auth array Array with 'user' and 'pass' for HTTP Auth
|
|
* @return $content string The URL contents
|
|
*/
|
|
|
|
function serendipity_request_url($uri, $method = 'GET', $contenttype = null, $data = null, $extra_options = null, $addData = null, $auth = null) {
|
|
global $serendipity;
|
|
|
|
require_once S9Y_PEAR_PATH . 'HTTP/Request2.php';
|
|
$options = array('follow_redirects' => true, 'max_redirects' => 5);
|
|
|
|
if (is_array($extra_options)) {
|
|
foreach ($extra_options as $okey => $oval) {
|
|
$options[$okey] = $oval;
|
|
}
|
|
}
|
|
serendipity_plugin_api::hook_event('backend_http_request', $options, $addData);
|
|
serendipity_request_start();
|
|
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
|
|
// On earlier PHP versions, the certificate validation fails. We deactivate it on them to restore the functionality we had with HTTP/Request1
|
|
$options['ssl_verify_peer'] = false;
|
|
}
|
|
|
|
switch (strtoupper($method)) {
|
|
case 'GET':
|
|
$http_method = HTTP_Request2::METHOD_GET;
|
|
break;
|
|
|
|
case 'PUT':
|
|
$http_method = HTTP_Request2::METHOD_PUT;
|
|
break;
|
|
|
|
case 'OPTIONS':
|
|
$http_method = HTTP_Request2::METHOD_OPTIONS;
|
|
break;
|
|
|
|
case 'HEAD':
|
|
$http_method = HTTP_Request2::METHOD_HEAD;
|
|
break;
|
|
|
|
case 'DELETE':
|
|
$http_method = HTTP_Request2::METHOD_DELETE;
|
|
break;
|
|
|
|
case 'TRACE':
|
|
$http_method = HTTP_Request2::METHOD_TRACE;
|
|
break;
|
|
|
|
case 'CONNECT':
|
|
$http_method = HTTP_Request2::METHOD_CONNECT;
|
|
break;
|
|
|
|
default:
|
|
case 'POST':
|
|
$http_method = HTTP_Request2::METHOD_POST;
|
|
break;
|
|
|
|
}
|
|
|
|
$req = new HTTP_Request2($uri, $http_method, $options);
|
|
if (isset($contenttype) && $contenttype !== null) {
|
|
$req->setHeader('Content-Type', $contenttype);
|
|
}
|
|
|
|
if (is_array($auth)) {
|
|
$req->setAuth($auth['user'], $auth['pass']);
|
|
}
|
|
|
|
if ($data != null) {
|
|
if (is_array($data)) {
|
|
$req->addPostParameter($data);
|
|
} else {
|
|
$req->setBody($data);
|
|
}
|
|
}
|
|
|
|
try {
|
|
$res = $req->send();
|
|
} catch (HTTP_Request2_Exception $e) {
|
|
serendipity_request_end();
|
|
return false;
|
|
}
|
|
|
|
|
|
$fContent = $res->getBody();
|
|
$serendipity['last_http_request'] = array(
|
|
'responseCode' => $res->getStatus(),
|
|
'effectiveUrl' => $res->getEffectiveUrl(),
|
|
'reasonPhrase' => $res->getReasonPhrase(),
|
|
'isRedirect' => $res->isRedirect(),
|
|
'cookies' => $res->getCookies(),
|
|
'version' => $res->getVersion(),
|
|
'header' => $res->getHeader(),
|
|
|
|
'object' => $res // forward compatibility for possible other checks
|
|
);
|
|
|
|
serendipity_request_end();
|
|
return $fContent;
|
|
}
|
|
|
|
/**
|
|
* Converts Array data to be used as a GET string
|
|
*
|
|
* @access public
|
|
* @param array The input array
|
|
* @param string An array prefix
|
|
* @param string How to join the array
|
|
* @return string The HTTP query string
|
|
*/
|
|
function serendipity_build_query(&$array, $array_prefix = null, $comb_char = '&') {
|
|
$ret = array();
|
|
if (!is_array($array)) {
|
|
return '';
|
|
}
|
|
|
|
foreach ($array as $k => $v) {
|
|
$newkey = urlencode($k);
|
|
if ($array_prefix) {
|
|
$newkey = $array_prefix . '[' . $newkey . ']';
|
|
}
|
|
if (is_array($v)) {
|
|
$ret[] = serendipity_build_query($v, $newkey, $comb_char);
|
|
} else {
|
|
$ret[] = $newkey . '=' . urlencode($v);
|
|
}
|
|
}
|
|
|
|
return implode($comb_char, $ret);
|
|
}
|
|
|
|
/**
|
|
* Picks a specified key from an array and returns it
|
|
*
|
|
* @access public
|
|
* @param array The input array
|
|
* @param string The key to search for
|
|
* @param string The default value to return when not found
|
|
* @return null
|
|
*/
|
|
function &serendipity_pickKey(&$array, $key, $default) {
|
|
if (!is_array($array)) {
|
|
return $default;
|
|
}
|
|
|
|
// array_key_exists() copies array, so is much slower.
|
|
if (in_array($key, array_keys($array))) {
|
|
if (isset($array[$key])) {
|
|
return $array[$key];
|
|
}
|
|
}
|
|
foreach($array AS $child) {
|
|
if (is_array($child) && isset($child[$key]) && !empty($child[$key])) {
|
|
return $child[$key];
|
|
}
|
|
}
|
|
|
|
return $default;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the current timestamp but only deals with minutes to optimize Database caching
|
|
* @access public
|
|
* @return timestamp
|
|
* @author Matthew Groeninger
|
|
*/
|
|
function serendipity_db_time() {
|
|
static $ts = null;
|
|
static $cache = 300; // Seconds to cache
|
|
|
|
if ($ts === null) {
|
|
$now = time();
|
|
$ts = $now - ($now % $cache) + $cache;
|
|
}
|
|
|
|
return $ts;
|
|
}
|
|
|
|
/**
|
|
* Inits the logger.
|
|
* @return null
|
|
*/
|
|
function serendipity_initLog() {
|
|
global $serendipity;
|
|
|
|
if (isset($serendipity['logLevel']) && $serendipity['logLevel'] !== 'Off') {
|
|
if ($serendipity['logLevel'] == 'debug') {
|
|
$log_level = Psr\Log\LogLevel::DEBUG;
|
|
} else {
|
|
$log_level = Psr\Log\LogLevel::ERROR;
|
|
}
|
|
$serendipity['logger'] = new Katzgrau\KLogger\Logger($serendipity['serendipityPath'] . '/templates_c/logs', $log_level);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether a given URL is valid to be locally requested
|
|
* @return boolean
|
|
*/
|
|
function serendipity_url_allowed($url) {
|
|
global $serendipity;
|
|
|
|
if ($serendipity['allowLocalURL']) {
|
|
return true;
|
|
}
|
|
|
|
$parts = @parse_url($url);
|
|
if (!is_array($parts) || empty($parts['host'])) {
|
|
return false;
|
|
}
|
|
|
|
$host = trim($parts['host'], '.');
|
|
if (preg_match('@^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)$@imsU', $host)) {
|
|
$ip = $host;
|
|
} else {
|
|
$ip = gethostbyname($host);
|
|
if ($ip === $host) {
|
|
$ip = false;
|
|
}
|
|
}
|
|
|
|
if ($ip) {
|
|
$ipparts = array_map('intval', explode('.', $ip));
|
|
if ( 127 === $ipparts[0] || 10 === $ipparts[0] || 0 === $ipparts[0]
|
|
|| ( 172 === $ipparts[0] && 16 <= $ipparts[1] && 31 >= $ipparts[1] )
|
|
|| ( 192 === $ipparts[0] && 168 === $ipparts[1])
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
define("serendipity_FUNCTIONS_LOADED", true);
|
|
/* vim: set sts=4 ts=4 expandtab : */
|