1<?php 2# Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team) 3# All rights reserved. See LICENSE file for licensing details 4 5if (IN_serendipity !== true) { 6 die ("Don't hack!"); 7} 8 9if (defined('S9Y_FRAMEWORK_FUNCTIONS')) { 10 return; 11} 12@define('S9Y_FRAMEWORK_FUNCTIONS', true); 13 14$serendipity['imageList'] = array(); 15 16include_once(S9Y_INCLUDE_PATH . 'include/db/db.inc.php'); 17include_once(S9Y_INCLUDE_PATH . 'include/compat.inc.php'); 18include_once(S9Y_INCLUDE_PATH . 'include/functions_config.inc.php'); 19include_once(S9Y_INCLUDE_PATH . 'include/plugin_api.inc.php'); 20include_once(S9Y_INCLUDE_PATH . 'include/functions_images.inc.php'); 21include_once(S9Y_INCLUDE_PATH . 'include/functions_installer.inc.php'); 22include_once(S9Y_INCLUDE_PATH . 'include/functions_entries.inc.php'); 23include_once(S9Y_INCLUDE_PATH . 'include/functions_comments.inc.php'); 24include_once(S9Y_INCLUDE_PATH . 'include/functions_permalinks.inc.php'); 25include_once(S9Y_INCLUDE_PATH . 'include/functions_smarty.inc.php'); 26 27/** 28 * Truncate a string to a specific length, multibyte aware. Appends '...' if successfully truncated 29 * 30 * @access public 31 * @param string Input string 32 * @param int Length the final string should have 33 * @return string Truncated string 34 */ 35function serendipity_truncateString($s, $len) { 36 if ( strlen($s) > ($len+3) ) { 37 $s = serendipity_mb('substr', $s, 0, $len) . '...'; 38 } 39 return $s; 40} 41 42/** 43 * Optionally turn on GZip Compression, if configured 44 * 45 * @access public 46 */ 47function serendipity_gzCompression() { 48 global $serendipity; 49 if (isset($serendipity['useGzip']) && serendipity_db_bool($serendipity['useGzip']) 50 && function_exists('ob_gzhandler') && extension_loaded('zlib') 51 && serendipity_ini_bool(ini_get('zlib.output_compression')) == false 52 && serendipity_ini_bool(ini_get('session.use_trans_sid')) == false) { 53 ob_start("ob_gzhandler"); 54 } 55} 56 57/** 58 * Returns a timestamp formatted according to the current Server timezone offset 59 * 60 * @access public 61 * @param int The timestamp you want to convert into the current server timezone. Defaults to "now". 62 * @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. 63 * @return int The final timestamp 64 */ 65function serendipity_serverOffsetHour($timestamp = null, $negative = false) { 66 global $serendipity; 67 68 if ($timestamp === null) { 69 $timestamp = time(); 70 } 71 72 if (empty($serendipity['serverOffsetHours']) || !is_numeric($serendipity['serverOffsetHours']) || $serendipity['serverOffsetHours'] == 0) { 73 return $timestamp; 74 } else { 75 return $timestamp + (($negative ? -$serendipity['serverOffsetHours'] : $serendipity['serverOffsetHours']) * 60 * 60); 76 } 77} 78 79/* Converts a date string (DD.MM.YYYY, YYYY-MM-DD, MM/DD/YYYY) into a unix timestamp 80 * 81 * @access public 82 * @param string The input date 83 * @return int The output unix timestamp 84 */ 85function &serendipity_convertToTimestamp($in) { 86 if (preg_match('@([0-9]+)([/\.-])([0-9]+)([/\.-])([0-9]+)@', $in, $m)) { 87 if ($m[2] != $m[4]) { 88 return $in; 89 } 90 91 switch($m[2]) { 92 case '.': 93 return mktime(0, 0, 0, /* month */ $m[3], /* day */ $m[1], /* year */ $m[5]); 94 break; 95 96 case '/': 97 return mktime(0, 0, 0, /* month */ $m[1], /* day */ $m[3], /* year */ $m[5]); 98 break; 99 100 case '-': 101 return mktime(0, 0, 0, /* month */ $m[3], /* day */ $m[5], /* year */ $m[1]); 102 break; 103 } 104 105 return $in; 106 } 107 108 return $in; 109} 110 111/** 112 * Format a timestamp 113 * 114 * This function can convert an input timestamp into specific PHP strftime() outputs, including applying necessary timezone calculations. 115 * 116 * @access public 117 * @param string Output format for the timestamp 118 * @param int Timestamp to use for displaying 119 * @param boolean Indicates, if timezone calculations shall be used. 120 * @param boolean Whether to use strftime or simply date 121 * @return string The formatted timestamp 122 */ 123function serendipity_strftime($format, $timestamp = null, $useOffset = true, $useDate = false) { 124 global $serendipity; 125 static $is_win_utf = null; 126 127 if ($is_win_utf === null) { 128 // Windows does not have UTF-8 locales. 129 $is_win_utf = (LANG_CHARSET == 'UTF-8' && strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? true : false); 130 } 131 132 if ($useDate) { 133 $out = date($format, $timestamp); 134 } else { 135 switch($serendipity['calendar']) { 136 default: 137 case 'gregorian': 138 if ($timestamp == null) { 139 $timestamp = serendipity_serverOffsetHour(); 140 } elseif ($useOffset) { 141 $timestamp = serendipity_serverOffsetHour($timestamp); 142 } 143 $out = strftime($format, $timestamp); 144 break; 145 146 case 'persian-utf8': 147 if ($timestamp == null) { 148 $timestamp = serendipity_serverOffsetHour(); 149 } elseif ($useOffset) { 150 $timestamp = serendipity_serverOffsetHour($timestamp); 151 } 152 153 require_once S9Y_INCLUDE_PATH . 'include/functions_calendars.inc.php'; 154 $out = persian_strftime_utf($format, $timestamp); 155 break; 156 } 157 } 158 159 if ($is_win_utf && (empty($serendipity['calendar']) || $serendipity['calendar'] == 'gregorian')) { 160 $out = utf8_encode($out); 161 } 162 163 return $out; 164} 165 166/** 167 * A wrapper function call for formatting Timestamps. 168 * 169 * Utilizes serendipity_strftime() and prepares the output timestamp with a few tweaks, and applies automatic uppercasing of the return. 170 * 171 * @see serendipity_strftime() 172 * @param string Output format for the timestamp 173 * @param int Timestamp to use for displaying 174 * @param boolean Indicates, if timezone calculations shall be used. 175 * @param boolean Whether to use strftime or simply date 176 * @return string The formatted timestamp 177 */ 178function serendipity_formatTime($format, $time, $useOffset = true, $useDate = false) { 179 static $cache; 180 if (!isset($cache)) { 181 $cache = array(); 182 } 183 184 if (!isset($cache[$format])) { 185 $cache[$format] = $format; 186 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 187 $cache[$format] = str_replace('%e', '%d', $cache[$format]); 188 } 189 } 190 191 return serendipity_mb('ucfirst', serendipity_strftime($cache[$format], (int)$time, $useOffset, $useDate)); 192} 193 194/** 195 * Fetches the list of available templates/themes/styles. 196 * 197 * @access public 198 * @param string Directory to search for a template [recursive use] 199 * @return array Sorted array of available template names 200 */ 201function serendipity_fetchTemplates($dir = '') { 202 global $serendipity; 203 204 $cdir = @opendir($serendipity['serendipityPath'] . $serendipity['templatePath'] . $dir); 205 $rv = array(); 206 if (!$cdir) { 207 return $rv; 208 } 209 while (($file = readdir($cdir)) !== false) { 210 if (is_dir($serendipity['serendipityPath'] . $serendipity['templatePath'] . $dir . $file) && !preg_match('@^(\.|CVS)@i', $file) && !file_exists($serendipity['serendipityPath'] . $serendipity['templatePath'] . $dir . $file . '/inactive.txt')) { 211 if (file_exists($serendipity['serendipityPath'] . $serendipity['templatePath'] . $dir . $file . '/info.txt')) { 212 $key = strtolower($file); 213 if (isset($rv[$key])) { 214 $key = $dir . $key; 215 } 216 $rv[$key] = $dir . $file; 217 } else { 218 $temp = serendipity_fetchTemplates($dir . $file . '/'); 219 if (count($temp) > 0) { 220 $rv = array_merge($rv, $temp); 221 } 222 } 223 } 224 } 225 closedir($cdir); 226 ksort($rv); 227 return $rv; 228} 229 230/** 231 * Get information about a specific theme/template/style 232 * 233 * @access public 234 * @param string Directory name of a theme 235 * @param string Absolute path to the templates [for use on CVS mounted directories] 236 * @return array Associative array if template information 237 */ 238function serendipity_fetchTemplateInfo($theme, $abspath = null) { 239 global $serendipity; 240 241 if ($abspath === null) { 242 $abspath = $serendipity['serendipityPath'] . $serendipity['templatePath']; 243 } 244 245 $lines = @file($abspath . $theme . '/info.txt'); 246 if ( !$lines ) { 247 return array(); 248 } 249 250 for($x=0; $x<count($lines); $x++) { 251 $j = preg_split('/([^\:]+)\:/', $lines[$x], -1, PREG_SPLIT_DELIM_CAPTURE); 252 if ($j[2]) { 253 $currSec = $j[1]; 254 $data[strtolower($currSec)][] = trim($j[2]); 255 } else { 256 $data[strtolower($currSec)][] = trim($j[0]); 257 } 258 } 259 260 foreach ($data as $k => $v) { 261 $data[$k] = implode("\n", $v); 262 } 263 264 if (@is_file($serendipity['templatePath'] . $theme . '/config.inc.php')) { 265 $data['custom_config'] = YES; 266 $data['custom_config_engine'] = $theme; 267 } 268 269 // Templates can depend on a possible "Engine" (i.e. "Engine: 2k11"). 270 // We support the fallback chain also of a template's configuration, so let's check each engine for a config file. 271 if (!empty($data['engine'])) { 272 $engines = explode(',', $data['engine']); 273 foreach($engines AS $engine) { 274 $engine = trim($engine); 275 if (empty($engine)) continue; 276 277 if (@is_file($serendipity['templatePath'] . $engine . '/config.inc.php')) { 278 $data['custom_config'] = YES; 279 $data['custom_config_engine'] = $engine; 280 } 281 } 282 } 283 284 if ( $theme != 'default' && $theme != 'default-rtl' 285 && @is_dir($serendipity['templatePath'] . $theme . '/admin') 286 && strtolower($data['backend']) == 'yes' ) { 287 288 $data['custom_admin_interface'] = YES; 289 } else { 290 $data['custom_admin_interface'] = NO; 291 } 292 293 return $data; 294} 295 296/** 297 * Recursively walks an 1-dimensional array to map parent IDs and depths, depending on the nested array set. 298 * 299 * 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. 300 * 301 * @access public 302 * @param array Input array to investigate [consecutively sliced for recursive calls] 303 * @param string Array index name to indicate the ID value of an array index 304 * @param string Array index name to indicate the PARENT ID value of an array index, matched against the $child_name value 305 * @param int The parent id to check an element against for recursive nesting 306 * @param int The current depth of the cycled array 307 * @return array The sorted and shiny polished result array 308 */ 309function serendipity_walkRecursive($ary, $child_name = 'id', $parent_name = 'parent_id', $parentid = 0, $depth = 0) { 310 global $serendipity; 311 static $_resArray; 312 static $_remain; 313 314 if (!is_array($ary) || sizeof($ary) == 0) { 315 return array(); 316 } 317 318 if ($parentid === VIEWMODE_THREADED) { 319 $parentid = 0; 320 } 321 322 if ($depth == 0) { 323 $_resArray = array(); 324 $_remain = $ary; 325 } 326 327 foreach($ary AS $key => $data) { 328 if ($parentid === VIEWMODE_LINEAR || !isset($data[$parent_name]) || $data[$parent_name] == $parentid) { 329 $data['depth'] = $depth; 330 $_resArray[] = $data; 331 unset($_remain[$key]); 332 if ($data[$child_name] && $parentid !== VIEWMODE_LINEAR ) { 333 serendipity_walkRecursive($ary, $child_name, $parent_name, $data[$child_name], ($depth+1)); 334 } 335 } 336 } 337 338 /* We are inside a recusive child, and we need to break out */ 339 if ($depth !== 0) { 340 return true; 341 } 342 343 if (count($_remain) > 0) { 344 // Remaining items need to be appended 345 foreach($_remain AS $key => $data) { 346 $data['depth'] = 0; 347 $_resArray[] = $data; 348 } 349 } 350 351 return $_resArray; 352} 353 354/** 355 * Fetch the list of Serendipity Authors 356 * 357 * @access public 358 * @param int Fetch only a specific User 359 * @param array Can contain an array of group IDs you only want to fetch authors of. 360 * @param boolean If set to TRUE, the amount of entries per author will also be returned 361 * @return array Result of the SQL query 362 */ 363function serendipity_fetchUsers($user = '', $group = null, $is_count = false) { 364 global $serendipity; 365 366 $where = ''; 367 if (!empty($user)) { 368 $where = "WHERE a.authorid = '" . (int)$user ."'"; 369 } 370 371 $query_select = ''; 372 $query_join = ''; 373 $query_group = ''; 374 $query_distinct = ''; 375 if ($is_count) { 376 $query_select = ", count(e.authorid) as artcount"; 377 $query_join = "LEFT OUTER JOIN {$serendipity['dbPrefix']}entries AS e 378 ON (a.authorid = e.authorid AND e.isdraft = 'false')"; 379 } 380 381 if ($is_count || $group != null) { 382 if ($serendipity['dbType'] == 'postgres' || 383 $serendipity['dbType'] == 'pdo-postgres') { 384 // Why does PostgreSQL keep doing this to us? :-) 385 $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'; 386 $query_distinct = 'DISTINCT'; 387 } else { 388 $query_group = 'GROUP BY a.authorid'; 389 $query_distinct = ''; 390 } 391 } 392 393 394 if ($group === null) { 395 $querystring = "SELECT $query_distinct 396 a.authorid, 397 a.realname, 398 a.username, 399 a.password, 400 a.hashtype, 401 a.mail_comments, 402 a.mail_trackbacks, 403 a.email, 404 a.userlevel, 405 a.right_publish 406 $query_select 407 FROM {$serendipity['dbPrefix']}authors AS a 408 $query_join 409 $where 410 $query_group 411 ORDER BY a.realname ASC"; 412 } else { 413 414 if ($group === 'hidden') { 415 $query_join .= "LEFT OUTER JOIN {$serendipity['dbPrefix']}groupconfig AS gc 416 ON (gc.property = 'hiddenGroup' AND gc.id = ag.groupid AND gc.value = 'true')"; 417 $where .= " AND gc.id IS NULL "; 418 } elseif (is_array($group)) { 419 foreach($group AS $idx => $groupid) { 420 $group[$idx] = (int)$groupid; 421 } 422 $group_sql = implode(', ', $group); 423 } else { 424 $group_sql = (int)$group; 425 } 426 427 $querystring = "SELECT $query_distinct 428 a.authorid, 429 a.realname, 430 a.username, 431 a.password, 432 a.hashtype, 433 a.mail_comments, 434 a.mail_trackbacks, 435 a.email, 436 a.userlevel, 437 a.right_publish 438 $query_select 439 FROM {$serendipity['dbPrefix']}authors AS a 440 LEFT OUTER JOIN {$serendipity['dbPrefix']}authorgroups AS ag 441 ON a.authorid = ag.authorid 442 LEFT OUTER JOIN {$serendipity['dbPrefix']}groups AS g 443 ON ag.groupid = g.id 444 $query_join 445 WHERE " . ($group_sql ? "g.id IN ($group_sql)" : '1=1') . " 446 $where 447 $query_group 448 ORDER BY a.realname ASC"; 449 } 450 451 return serendipity_db_query($querystring); 452} 453 454 455/** 456 * Sends a Mail with Serendipity formatting 457 * 458 * @access public 459 * @param string The recipient address of the mail 460 * @param string The subject of the mail 461 * @param string The body of the mail 462 * @param string The sender mail address of the mail 463 * @param array additional headers to pass to the E-Mail 464 * @param string The name of the sender 465 * @return int Return code of the PHP mail() function 466 */ 467function serendipity_sendMail($to, $subject, $message, $fromMail, $headers = NULL, $fromName = NULL) { 468 global $serendipity; 469 470 if (!is_null($headers) && !is_array($headers)) { 471 trigger_error(__FUNCTION__ . ': $headers must be either an array or null', E_USER_ERROR); 472 } 473 474 if (is_null($fromName) || empty($fromName)) { 475 $fromName = $serendipity['blogTitle']; 476 } 477 478 if (is_null($fromMail) || empty($fromMail)) { 479 $fromMail = $to; 480 } 481 482 if (is_null($headers)) { 483 $headers = array(); 484 } 485 486 // Fix special characters 487 $fromName = str_replace(array('"', "\r", "\n"), array("'", '', ''), $fromName); 488 $fromMail = str_replace(array("\r","\n"), array('', ''), $fromMail); 489 490 // Prefix all mail with weblog title 491 $subject = '['. $serendipity['blogTitle'] . '] '. $subject; 492 493 // Append signature to every mail 494 $message .= "\n" . sprintf(SIGNATURE, $serendipity['blogTitle']); 495 496 $maildata = array( 497 'to' => &$to, 498 'subject' => &$subject, 499 'fromName' => &$fromName, 500 'fromMail' => &$fromMail, 501 'blogMail' => $serendipity['blogMail'], 502 'version' => 'Serendipity' . ($serendipity['expose_s9y'] ? '/' . $serendipity['version'] : ''), 503 'legacy' => true, 504 'headers' => &$headers, 505 'message' => &$message 506 ); 507 508 serendipity_plugin_api::hook_event('backend_sendmail', $maildata, LANG_CHARSET); 509 510 // This routine can be overridden by a plugin. 511 if ($maildata['legacy']) { 512 // Check for mb_* function, and use it to encode headers etc. */ 513 if (function_exists('mb_encode_mimeheader')) { 514 // mb_encode_mimeheader function insertes linebreaks after 74 chars. 515 // Usually this is according to spec, but for us it caused more trouble than 516 // it prevented. 517 // Regards to Mark Kronsbein for finding this issue! 518 $maildata['subject'] = str_replace(array("\n", "\r"), array('', ''), mb_encode_mimeheader($maildata['subject'], LANG_CHARSET)); 519 $maildata['fromName'] = str_replace(array("\n", "\r"), array('', ''), mb_encode_mimeheader($maildata['fromName'], LANG_CHARSET)); 520 } 521 522 523 // Always add these headers 524 if (!empty($maildata['blogMail'])) { 525 $maildata['headers'][] = 'From: "'. $maildata['fromName'] .'" <'. $maildata['blogMail'] .'>'; 526 } 527 $maildata['headers'][] = 'Reply-To: "'. $maildata['fromName'] .'" <'. $maildata['fromMail'] .'>'; 528 if ($serendipity['expose_s9y']) { 529 $maildata['headers'][] = 'X-Mailer: ' . $maildata['version']; 530 $maildata['headers'][] = 'X-Engine: PHP/'. phpversion(); 531 } 532 $maildata['headers'][] = 'Message-ID: <'. md5(microtime() . uniqid(time())) .'@'. $_SERVER['HTTP_HOST'] .'>'; 533 $maildata['headers'][] = 'MIME-Version: 1.0'; 534 $maildata['headers'][] = 'Precedence: bulk'; 535 $maildata['headers'][] = 'Content-Type: text/plain; charset=' . LANG_CHARSET; 536 $maildata['headers'][] = 'Auto-Submitted: auto-generated'; 537 538 if (LANG_CHARSET == 'UTF-8') { 539 if (function_exists('imap_8bit') && !$serendipity['forceBase64']) { 540 $maildata['headers'][] = 'Content-Transfer-Encoding: quoted-printable'; 541 $maildata['message'] = str_replace("\r\n","\n",imap_8bit($maildata['message'])); 542 } else { 543 $maildata['headers'][] = 'Content-Transfer-Encoding: base64'; 544 $maildata['message'] = chunk_split(base64_encode($maildata['message'])); 545 } 546 } 547 } 548 549 if ($serendipity['dumpMail']) { 550 $fp = fopen($serendipity['serendipityPath'] . '/templates_c/mail.log', 'a'); 551 fwrite($fp, date('Y-m-d H:i') . "\n" . print_r($maildata, true)); 552 fclose($fp); 553 } 554 555 if (!isset($maildata['skip_native']) && !empty($maildata['to'])) { 556 return mail($maildata['to'], $maildata['subject'], $maildata['message'], implode("\n", $maildata['headers'])); 557 } 558} 559 560/** 561 * Fetch all references (links) from a given entry ID 562 * 563 * @access public 564 * @param int The entry ID 565 * @return array The SQL result containing the references/links of an entry 566 */ 567function serendipity_fetchReferences($id) { 568 global $serendipity; 569 570 $query = "SELECT name,link FROM {$serendipity['dbPrefix']}references WHERE entry_id = '" . (int)$id . "' AND type = ''"; 571 572 return serendipity_db_query($query); 573} 574 575 576/** 577 * Encode a string to UTF-8, if not already in UTF-8 format. 578 * 579 * @access public 580 * @param string The input string 581 * @return string The output string 582 */ 583function serendipity_utf8_encode($string) { 584 if (strtolower(LANG_CHARSET) != 'utf-8') { 585 if (function_exists('iconv')) { 586 $new = iconv(LANG_CHARSET, 'UTF-8', $string); 587 if ($new !== false) { 588 return $new; 589 } else { 590 return utf8_encode($string); 591 } 592 } else if (function_exists('mb_convert_encoding')) { 593 return mb_convert_encoding($string, 'UTF-8', LANG_CHARSET); 594 } else { 595 return utf8_encode($string); 596 } 597 } else { 598 return $string; 599 } 600} 601 602/** 603 * Create a link that can be used within a RSS feed to indicate a permalink for an entry or comment 604 * 605 * @access public 606 * @param array The input entry array 607 * @param boolean Toggle whether the link will be for a COMMENT [true] or an ENTRY [false] 608 * @return string A permalink for the given entry 609 */ 610function serendipity_rss_getguid($entry, $comments = false) { 611 global $serendipity; 612 613 $id = (isset($entry['entryid']) && $entry['entryid'] != '' ? $entry['entryid'] : $entry['id']); 614 615 // When using %id%, we can make the GUID shorter and independent from the title. 616 // If not using %id%, the entryid needs to be used for uniqueness. 617 if (stristr($serendipity['permalinkStructure'], '%id%') !== FALSE) { 618 $title = 'guid'; 619 } else { 620 $title = $id; 621 } 622 623 $guid = serendipity_archiveURL( 624 $id, 625 $title, 626 'baseURL', 627 true, 628 array('timestamp' => $entry['timestamp']) 629 ); 630 631 if ($comments == true) { 632 $guid .= '#c' . $entry['commentid']; 633 } 634 635 return $guid; 636} 637 638/** 639 * Perform some replacement calls to make valid XHTML content 640 * 641 * jbalcorn: starter function to clean up xhtml for atom feed. Add things to this as we find common 642 * mistakes, unless someone finds a better way to do this. 643 * DONE: 644 * since someone encoded all the urls, we can now assume any amp followed by 645 * whitespace or a HTML tag (i.e. &<br /> )should be 646 * encoded and most not with a space are intentional 647 * 648 * @access public 649 * @param string Input HTML code 650 * @return string Cleaned HTML code 651 */ 652function xhtml_cleanup($html) { 653 static $p = array( 654 '/\&([\s\<])/', // ampersand followed by whitespace or tag 655 '/\&$/', // ampersand at end of body 656 '/<(br|hr|img)([^\/>]*)>/i', // unclosed br tag - attributes included 657 '/\ /' // Protect whitespace 658 ); 659 660 static $r = array( 661 '&\1', 662 '&', 663 '<\1\2 />', 664 ' ' 665 ); 666 667 return preg_replace($p, $r, $html); 668} 669 670/** 671 * Fetch user data for a specific Serendipity author 672 * 673 * @access public 674 * @param int The requested author id 675 * @return array The SQL result array 676 */ 677function serendipity_fetchAuthor($author) { 678 global $serendipity; 679 680 return serendipity_db_query("SELECT * FROM {$serendipity['dbPrefix']}authors WHERE " . (is_numeric($author) ? "authorid={$author};" : "username='" . serendipity_db_escape_string($author) . "';")); 681} 682 683/** 684 * Split a filename into basename and extension parts 685 * 686 * @access public 687 * @param string Filename 688 * @return array Return array containing the basename and file extension 689 */ 690function serendipity_parseFileName($file) { 691 $x = explode('.', $file); 692 if (count($x)>1){ 693 $suf = array_pop($x); 694 $f = @implode('.', $x); 695 return array($f, $suf); 696 } 697 else { 698 return array($file,''); 699 } 700} 701 702/** 703 * Track the referer to a specific Entry ID 704 * 705 * @access public 706 * @param int Entry ID 707 * @return null 708 */ 709function serendipity_track_referrer($entry = 0) { 710 global $serendipity; 711 712 // Tracking disabled. 713 if ($serendipity['trackReferrer'] === false) { 714 return; 715 } 716 717 if (isset($_SERVER['HTTP_REFERER'])) { 718 if (stristr($_SERVER['HTTP_REFERER'], $serendipity['baseURL']) !== false) { 719 return; 720 } 721 722 if (!isset($serendipity['_blockReferer']) || !is_array($serendipity['_blockReferer'])) { 723 // Only generate an array once per call 724 $serendipity['_blockReferer'] = array(); 725 $serendipity['_blockReferer'] = @explode(';', $serendipity['blockReferer']); 726 } 727 728 $url_parts = parse_url($_SERVER['HTTP_REFERER']); 729 $host_parts = explode('.', $url_parts['host']); 730 if (!$url_parts['host'] || 731 strstr($url_parts['host'], $_SERVER['SERVER_NAME'])) { 732 return; 733 } 734 735 foreach($serendipity['_blockReferer'] AS $idx => $hostname) { 736 if (@strstr($url_parts['host'], $hostname)) { 737 return; 738 } 739 } 740 741 if (rand(0, 100) < 1) { 742 serendipity_track_referrer_gc(); 743 } 744 745 $ts = serendipity_db_get_interval('ts'); 746 $interval = serendipity_db_get_interval('interval', 900); 747 748 $url_parts['query'] = substr($url_parts['query'], 0, 255); 749 750 $suppressq = "SELECT count(1) 751 FROM $serendipity[dbPrefix]suppress 752 WHERE ip = '" . serendipity_db_escape_string($_SERVER['REMOTE_ADDR']) . "' 753 AND scheme = '" . serendipity_db_escape_string($url_parts['scheme']) . "' 754 AND port = '" . serendipity_db_escape_string($url_parts['port']) . "' 755 AND host = '" . serendipity_db_escape_string($url_parts['host']) . "' 756 AND path = '" . serendipity_db_escape_string($url_parts['path']) . "' 757 AND query = '" . serendipity_db_escape_string($url_parts['query']) . "' 758 AND last > $ts - $interval"; 759 760 $suppressp = "DELETE FROM $serendipity[dbPrefix]suppress 761 WHERE ip = '" . serendipity_db_escape_string($_SERVER['REMOTE_ADDR']) . "' 762 AND scheme = '" . serendipity_db_escape_string($url_parts['scheme']) . "' 763 AND host = '" . serendipity_db_escape_string($url_parts['host']) . "' 764 AND port = '" . serendipity_db_escape_string($url_parts['port']) . "' 765 AND query = '" . serendipity_db_escape_string($url_parts['query']) . "' 766 AND path = '" . serendipity_db_escape_string($url_parts['path']) . "'"; 767 $suppressu = "INSERT INTO $serendipity[dbPrefix]suppress 768 (ip, last, scheme, host, port, path, query) 769 VALUES ( 770 '" . serendipity_db_escape_string($_SERVER['REMOTE_ADDR']) . "', 771 $ts, 772 '" . serendipity_db_escape_string($url_parts['scheme']) . "', 773 '" . serendipity_db_escape_string($url_parts['host']) . "', 774 '" . serendipity_db_escape_string($url_parts['port']) . "', 775 '" . serendipity_db_escape_string($url_parts['path']) . "', 776 '" . serendipity_db_escape_string($url_parts['query']) . "' 777 )"; 778 779 $count = serendipity_db_query($suppressq, true); 780 781 if ($count[0] == 0) { 782 serendipity_db_query($suppressu); 783 return; 784 } 785 786 serendipity_db_query($suppressp); 787 serendipity_db_query($suppressu); 788 789 serendipity_track_url('referrers', $_SERVER['HTTP_REFERER'], $entry); 790 } 791} 792 793/** 794 * Garbage Collection for suppressed referrers 795 * 796 * "Bad" referrers, that only occurred once to your entry are put within a 797 * SUPPRESS database table. Entries contained there will be cleaned up eventually. 798 * 799 * @access public 800 * @return null 801 */ 802function serendipity_track_referrer_gc() { 803 global $serendipity; 804 805 $ts = serendipity_db_get_interval('ts'); 806 $interval = serendipity_db_get_interval('interval', 900); 807 $gc = "DELETE FROM $serendipity[dbPrefix]suppress WHERE last <= $ts - $interval"; 808 serendipity_db_query($gc); 809} 810 811/** 812 * Track a URL used in your Blog (Exit-Tracking) 813 * 814 * @access public 815 * @param string Name of the DB table where to store the link (exits|referrers) 816 * @param string The URL to track 817 * @param int The Entry ID to relate the track to 818 * @return null 819 */ 820function serendipity_track_url($list, $url, $entry_id = 0) { 821 global $serendipity; 822 823 $url_parts = parse_url($url); 824 $url_parts['query'] = substr($url_parts['query'], 0, 255); 825 826 serendipity_db_query( 827 @sprintf( 828 "UPDATE %s%s 829 SET count = count + 1 830 WHERE scheme = '%s' 831 AND host = '%s' 832 AND port = '%s' 833 AND path = '%s' 834 AND query = '%s' 835 AND day = '%s' 836 %s", 837 838 $serendipity['dbPrefix'], 839 $list, 840 serendipity_db_escape_string($url_parts['scheme']), 841 serendipity_db_escape_string($url_parts['host']), 842 serendipity_db_escape_string($url_parts['port']), 843 serendipity_db_escape_string($url_parts['path']), 844 serendipity_db_escape_string($url_parts['query']), 845 date('Y-m-d'), 846 ($entry_id != 0) ? "AND entry_id = '". (int)$entry_id ."'" : '' 847 ) 848 ); 849 850 if (serendipity_db_affected_rows() == 0) { 851 serendipity_db_query( 852 sprintf( 853 "INSERT INTO %s%s 854 (entry_id, day, count, scheme, host, port, path, query) 855 VALUES (%d, '%s', 1, '%s', '%s', '%s', '%s', '%s')", 856 857 $serendipity['dbPrefix'], 858 $list, 859 (int)$entry_id, 860 date('Y-m-d'), 861 serendipity_db_escape_string($url_parts['scheme']), 862 serendipity_db_escape_string($url_parts['host']), 863 serendipity_db_escape_string($url_parts['port']), 864 serendipity_db_escape_string($url_parts['path']), 865 serendipity_db_escape_string($url_parts['query']) 866 ) 867 ); 868 } 869} 870 871/** 872 * Display the list of top referrers 873 * 874 * @access public 875 * @see serendipity_displayTopUrlList() 876 * @param int Number of referrers to show 877 * @param boolean Whether to use HTML links for URLs 878 * @param int Interval for which the top referrers are aggregated 879 * @return string List of Top referrers 880 */ 881function serendipity_displayTopReferrers($limit = 10, $use_links = true, $interval = 7) { 882 return serendipity_displayTopUrlList('referrers', $limit, $use_links, $interval); 883} 884 885/** 886 * Display the list of top exits 887 * 888 * @access public 889 * @see serendipity_displayTopUrlList() 890 * @param int Number of exits to show 891 * @param boolean Whether to use HTML links for URLs 892 * @param int Interval for which the top exits are aggregated 893 * @return string List of Top exits 894 */ 895function serendipity_displayTopExits($limit = 10, $use_links = true, $interval = 7) { 896 return serendipity_displayTopUrlList('exits', $limit, $use_links, $interval); 897} 898 899/** 900 * Display HTML output data of a Exit/Referrer list 901 * 902 * @access public 903 * @see serendipity_displayTopExits() 904 * @see serendipity_displayTopReferrers() 905 * @param string Name of the DB table to show data from (exits|referrers) 906 * @param boolean Whether to use HTML links for URLs 907 * @param int Interval for which the top exits are aggregated 908 * @return 909 */ 910function serendipity_displayTopUrlList($list, $limit, $use_links = true, $interval = 7) { 911 global $serendipity; 912 913 if ($limit){ 914 $limit = serendipity_db_limit_sql($limit); 915 } 916 917 /* HACK */ 918 if (preg_match('/^mysqli?/', $serendipity['dbType'])) { 919 /* Nonportable SQL due to MySQL date functions, 920 * but produces rolling 7 day totals, which is more 921 * interesting 922 */ 923 $query = "SELECT scheme, host, SUM(count) AS total 924 FROM {$serendipity['dbPrefix']}$list 925 WHERE day > date_sub(current_date, interval " . (int)$interval . " day) 926 GROUP BY host 927 ORDER BY total DESC, host 928 $limit"; 929 } else { 930 /* Portable version of the same query */ 931 $query = "SELECT scheme, host, SUM(count) AS total 932 FROM {$serendipity['dbPrefix']}$list 933 GROUP BY scheme, host 934 ORDER BY total DESC, host 935 $limit"; 936 } 937 938 $rows = serendipity_db_query($query); 939 $output = "<span class='serendipityReferer'>"; 940 if (is_array($rows)) { 941 foreach ($rows as $row) { 942 if ($use_links) { 943 $output .= sprintf( 944 '<span class="block_level"><a href="%1$s://%2$s" title="%2$s" >%2$s</a> (%3$s) </span>', 945 serendipity_specialchars($row['scheme']), 946 serendipity_specialchars($row['host']), 947 serendipity_specialchars($row['total']) 948 ); 949 } else { 950 $output .= sprintf( 951 '<span class="block_level">%1$s (%2$s) </span>', 952 serendipity_specialchars($row['host']), 953 serendipity_specialchars($row['total']) 954 ); 955 } 956 } 957 } 958 $output .= "</span>"; 959 return $output; 960} 961 962/** 963 * Return either HTML or XHTML code for an '<a target...> attribute. 964 * 965 * @access public 966 * @param string The target to use (_blank, _parent, ...) 967 * @return string HTML string containig the valid markup for the target attribute. 968 */ 969function serendipity_xhtml_target($target) { 970 global $serendipity; 971 972 if ($serendipity['enablePopup'] != true) 973 return ""; 974 975 return ' onclick="window.open(this.href, \'target' . time() . '\'); return false;" '; 976} 977 978/** 979 * Parse a URI portion to return which RSS Feed version was requested 980 * 981 * @access public 982 * @param string Name of the core URI part 983 * @param string File extension name of the URI 984 * @return string RSS feed type/version 985 */ 986function serendipity_discover_rss($name, $ext) { 987 static $default = '2.0'; 988 989 /* Detect type */ 990 if ($name == 'comments') { 991 $type = 'comments'; 992 } elseif ($name == 'comments_and_trackbacks') { 993 $type = 'comments_and_trackbacks'; 994 } elseif ($name == 'trackbacks') { 995 $type = 'trackbacks'; 996 } else { 997 $type = 'content'; 998 } 999 1000 /* Detect version */ 1001 if ($name == 'atom' || $name == 'atom10' || $ext == 'atom') { 1002 $ver = 'atom1.0'; 1003 } elseif ($name == 'atom03') { 1004 $ver = 'atom0.3'; 1005 } elseif ($name == 'opml' || $ext == 'opml') { 1006 $ver = 'opml1.0'; 1007 } elseif ($ext == 'rss') { 1008 $ver = '0.91'; 1009 } elseif ($ext == 'rss1') { 1010 $ver = '1.0'; 1011 } else { 1012 $ver = $default; 1013 } 1014 1015 return array($ver, $type); 1016} 1017 1018/** 1019 * Check whether an input string contains "evil" characters used for HTTP Response Splitting 1020 * 1021 * @access public 1022 * @param string String to check for evil characters 1023 * @return boolean Return true on success, false on failure 1024 */ 1025function serendipity_isResponseClean($d) { 1026 return (strpos($d, "\r") === false && strpos($d, "\n") === false && stripos($d, "%0A") === false && stripos($d, "%0D") === false); 1027} 1028 1029/** 1030 * Create a new Category 1031 * 1032 * @access public 1033 * @param string The new category name 1034 * @param string The new category description 1035 * @param int The category owner 1036 * @param string An icon representing the category 1037 * @param int A possible parentid to a category 1038 * @return int The new category's ID 1039 */ 1040function serendipity_addCategory($name, $desc, $authorid, $icon, $parentid) { 1041 global $serendipity; 1042 $query = "INSERT INTO {$serendipity['dbPrefix']}category 1043 (category_name, category_description, authorid, category_icon, parentid, category_left, category_right) 1044 VALUES 1045 ('". serendipity_db_escape_string($name) ."', 1046 '". serendipity_db_escape_string($desc) ."', 1047 ". (int)$authorid .", 1048 '". serendipity_db_escape_string($icon) ."', 1049 ". (int)$parentid .", 1050 0, 1051 0)"; 1052 1053 serendipity_db_query($query); 1054 $cid = serendipity_db_insert_id('category', 'categoryid'); 1055 serendipity_plugin_api::hook_event('backend_category_addNew', $cid); 1056 1057 $data = array( 1058 'categoryid' => $cid, 1059 'category_name' => $name, 1060 'category_description' => $desc 1061 ); 1062 1063 serendipity_insertPermalink($data, 'category'); 1064 return $cid; 1065} 1066 1067/** 1068 * Update an existing category 1069 * 1070 * @access public 1071 * @param int Category ID to update 1072 * @param string The new category name 1073 * @param string The new category description 1074 * @param int The new category owner 1075 * @param string The new category icon 1076 * @param int The new category parent ID 1077 * @param int The new category sort order 1078 * @param int The new category subcat hiding 1079 * @return null 1080 */ 1081function serendipity_updateCategory($cid, $name, $desc, $authorid, $icon, $parentid, $sort_order = 0, $hide_sub = 0, $admin_category = '') { 1082 global $serendipity; 1083 1084 $query = "UPDATE {$serendipity['dbPrefix']}category 1085 SET category_name = '". serendipity_db_escape_string($name) ."', 1086 category_description = '". serendipity_db_escape_string($desc) ."', 1087 authorid = ". (int)$authorid .", 1088 category_icon = '". serendipity_db_escape_string($icon) ."', 1089 parentid = ". (int)$parentid .", 1090 sort_order = ". (int)$sort_order . ", 1091 hide_sub = ". (int)$hide_sub . " 1092 WHERE categoryid = ". (int)$cid ." 1093 $admin_category"; 1094 serendipity_db_query($query); 1095 serendipity_plugin_api::hook_event('backend_category_update', $cid); 1096 1097 $data = array( 1098 'id' => $cid, 1099 'categoryid' => $cid, 1100 'category_name' => $name, 1101 'category_description' => $desc 1102 ); 1103 1104 serendipity_updatePermalink($data, 'category'); 1105} 1106 1107/** 1108 * Ends a session, so that while a file requests happens, Serendipity can work on in that session 1109 */ 1110function serendipity_request_start() { 1111 @session_write_close(); 1112 return true; 1113} 1114 1115/** 1116 * Continues a session after a file request 1117 */ 1118function serendipity_request_end() { 1119 @session_start(); 1120 return true; 1121} 1122 1123/* Request the contents of an URL, API wrapper 1124 * @param $uri string The URL to fetch 1125 * @param $method string HTTP method (GET/POST/PUT/OPTIONS...) 1126 * @param $contenttype string optional HTTP content type 1127 * @param $contenttype mixed optional extra data (i.e. POST body), can be an array 1128 * @param $extra_options array Extra options for HTTP_Request $options array (can override defaults) 1129 * @param $addData string possible extra event addData declaration for 'backend_http_request' hook 1130 * @param $auth array Array with 'user' and 'pass' for HTTP Auth 1131 * @return $content string The URL contents 1132 */ 1133 1134function serendipity_request_url($uri, $method = 'GET', $contenttype = null, $data = null, $extra_options = null, $addData = null, $auth = null) { 1135 global $serendipity; 1136 1137 require_once S9Y_PEAR_PATH . 'HTTP/Request2.php'; 1138 $options = array('follow_redirects' => true, 'max_redirects' => 5); 1139 1140 if (is_array($extra_options)) { 1141 foreach($extra_options AS $okey => $oval) { 1142 $options[$okey] = $oval; 1143 } 1144 } 1145 serendipity_plugin_api::hook_event('backend_http_request', $options, $addData); 1146 serendipity_request_start(); 1147 if (version_compare(PHP_VERSION, '5.6.0', '<')) { 1148 // On earlier PHP versions, the certificate validation fails. We deactivate it on them to restore the functionality we had with HTTP/Request1 1149 $options['ssl_verify_peer'] = false; 1150 } 1151 1152 switch(strtoupper($method)) { 1153 case 'GET': 1154 $http_method = HTTP_Request2::METHOD_GET; 1155 break; 1156 1157 case 'PUT': 1158 $http_method = HTTP_Request2::METHOD_PUT; 1159 break; 1160 1161 case 'OPTIONS': 1162 $http_method = HTTP_Request2::METHOD_OPTIONS; 1163 break; 1164 1165 case 'HEAD': 1166 $http_method = HTTP_Request2::METHOD_HEAD; 1167 break; 1168 1169 case 'DELETE': 1170 $http_method = HTTP_Request2::METHOD_DELETE; 1171 break; 1172 1173 case 'TRACE': 1174 $http_method = HTTP_Request2::METHOD_TRACE; 1175 break; 1176 1177 case 'CONNECT': 1178 $http_method = HTTP_Request2::METHOD_CONNECT; 1179 break; 1180 1181 default: 1182 case 'POST': 1183 $http_method = HTTP_Request2::METHOD_POST; 1184 break; 1185 1186 } 1187 1188 $req = new HTTP_Request2($uri, $http_method, $options); 1189 if (isset($contenttype) && $contenttype !== null) { 1190 $req->setHeader('Content-Type', $contenttype); 1191 } 1192 1193 if (is_array($auth)) { 1194 $req->setAuth($auth['user'], $auth['pass']); 1195 } 1196 1197 if ($data != null) { 1198 if (is_array($data)) { 1199 $req->addPostParameter($data); 1200 } else { 1201 $req->setBody($data); 1202 } 1203 } 1204 1205 try { 1206 $res = $req->send(); 1207 } catch (HTTP_Request2_Exception $e) { 1208 serendipity_request_end(); 1209 return false; 1210 } 1211 1212 1213 $fContent = $res->getBody(); 1214 $serendipity['last_http_request'] = array( 1215 'responseCode' => $res->getStatus(), 1216 'effectiveUrl' => $res->getEffectiveUrl(), 1217 'reasonPhrase' => $res->getReasonPhrase(), 1218 'isRedirect' => $res->isRedirect(), 1219 'cookies' => $res->getCookies(), 1220 'version' => $res->getVersion(), 1221 'header' => $res->getHeader(), 1222 1223 'object' => $res // forward compatibility for possible other checks 1224 ); 1225 1226 serendipity_request_end(); 1227 return $fContent; 1228} 1229 1230 1231if (!function_exists('microtime_float')) { 1232 /** 1233 * Get current timestamp as microseconds 1234 * 1235 * @access public 1236 * @return float the time 1237 */ 1238 function microtime_float() { 1239 list($usec, $sec) = explode(" ", microtime()); 1240 return ((float)$usec + (float)$sec); 1241 } 1242} 1243 1244/** 1245 * Converts Array data to be used as a GET string 1246 * 1247 * @access public 1248 * @param array The input array 1249 * @param string An array prefix 1250 * @param string How to join the array 1251 * @return string The HTTP query string 1252 */ 1253function serendipity_build_query(&$array, $array_prefix = null, $comb_char = '&') { 1254 $ret = array(); 1255 if (!is_array($array)) { 1256 return ''; 1257 } 1258 1259 foreach ($array as $k => $v) { 1260 $newkey = urlencode($k); 1261 if ($array_prefix) { 1262 $newkey = $array_prefix . '[' . $newkey . ']'; 1263 } 1264 if (is_array($v)) { 1265 $ret[] = serendipity_build_query($v, $newkey, $comb_char); 1266 } else { 1267 $ret[] = $newkey . '=' . urlencode($v); 1268 } 1269 } 1270 1271 return implode($comb_char, $ret); 1272} 1273 1274/** 1275 * Picks a specified key from an array and returns it 1276 * 1277 * @access public 1278 * @param array The input array 1279 * @param string The key to search for 1280 * @param string The default value to return when not found 1281 * @return null 1282 */ 1283function &serendipity_pickKey(&$array, $key, $default) { 1284 if (!is_array($array)) { 1285 return $default; 1286 } 1287 1288 // array_key_exists() copies array, so is much slower. 1289 if (in_array($key, array_keys($array))) { 1290 if (isset($array[$key])) { 1291 return $array[$key]; 1292 } 1293 } 1294 foreach($array AS $child) { 1295 if (is_array($child) && isset($child[$key]) && !empty($child[$key])) { 1296 return $child[$key]; 1297 } 1298 } 1299 1300 return $default; 1301} 1302 1303/** 1304 * Retrieves the current timestamp but only deals with minutes to optimize Database caching 1305 * @access public 1306 * @return timestamp 1307 * @author Matthew Groeninger 1308 */ 1309function serendipity_db_time() { 1310 static $ts = null; 1311 static $cache = 300; // Seconds to cache 1312 1313 if ($ts === null) { 1314 $now = time(); 1315 $ts = $now - ($now % $cache) + $cache; 1316 } 1317 1318 return $ts; 1319} 1320 1321/** 1322 * Inits the logger. 1323 * @return null 1324 */ 1325function serendipity_initLog() { 1326 global $serendipity; 1327 1328 if (isset($serendipity['logLevel']) && $serendipity['logLevel'] !== 'Off') { 1329 if ($serendipity['logLevel'] == 'debug') { 1330 $log_level = Psr\Log\LogLevel::DEBUG; 1331 } else { 1332 $log_level = Psr\Log\LogLevel::ERROR; 1333 } 1334 $serendipity['logger'] = new Katzgrau\KLogger\Logger($serendipity['serendipityPath'] . '/templates_c/logs', $log_level); 1335 } 1336} 1337 1338/** 1339 * Check whether a given URL is valid to be locally requested 1340 * @return boolean 1341 */ 1342function serendipity_url_allowed($url) { 1343 global $serendipity; 1344 1345 if ($serendipity['allowLocalURL']) { 1346 return true; 1347 } 1348 1349 $parts = @parse_url($url); 1350 if (!is_array($parts) || empty($parts['host'])) { 1351 return false; 1352 } 1353 1354 $host = trim($parts['host'], '.'); 1355 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)) { 1356 $ip = $host; 1357 } else { 1358 $ip = gethostbyname($host); 1359 if ($ip === $host) { 1360 $ip = false; 1361 } 1362 } 1363 1364 if ($ip) { 1365 $ipparts = array_map('intval', explode('.', $ip)); 1366 if ( 127 === $ipparts[0] || 10 === $ipparts[0] || 0 === $ipparts[0] 1367 || ( 172 === $ipparts[0] && 16 <= $ipparts[1] && 31 >= $ipparts[1] ) 1368 || ( 192 === $ipparts[0] && 168 === $ipparts[1]) 1369 ) { 1370 return false; 1371 } 1372 } 1373 1374 return true; 1375} 1376 1377use voku\cache\Cache; 1378// Configure voku/simple-cache to use templates_c as directory for the opcache files, the fallback 1379// when Memcached and Redis are not used. Returns the configured cache object. Used internally by 1380// the other cache functions, you most likely never need to call this. 1381function serendipity_setupCache() { 1382 $cacheManager = new \voku\cache\CacheAdapterAutoManager(); 1383 1384 $cacheManager->addAdapter( 1385 \voku\cache\AdapterOpCache::class, 1386 static function () { 1387 global $serendipity; 1388 $cacheDir = $serendipity['serendipityPath'] . '/templates_c/simple_cache'; 1389 1390 return $cacheDir; 1391 } 1392 ); 1393 1394 $cacheManager->addAdapter( 1395 \voku\cache\AdapterArray::class 1396 ); 1397 1398 $cache = new Cache( 1399 null, 1400 null, 1401 false, 1402 true, 1403 false, 1404 false, 1405 false, 1406 false, 1407 '', 1408 $cacheManager, 1409 false 1410 ); 1411 return $cache; 1412} 1413 1414function serendipity_cleanCache() { 1415 $cache = serendipity_setupCache(); 1416 return $cache->removeAll(); 1417} 1418 1419function serendipity_cacheItem($key, $item, $ttl = 3600) { 1420 $cache = serendipity_setupCache(); 1421 return $cache->setItem($key, $item, $ttl); 1422} 1423 1424function serendipity_getCacheItem($key) { 1425 $cache = serendipity_setupCache(); 1426 return $cache->getItem($key); 1427} 1428 1429define("serendipity_FUNCTIONS_LOADED", true); 1430/* vim: set sts=4 ts=4 expandtab : */ 1431