Permalink
Fetching contributors…
Cannot retrieve contributors at this time
6987 lines (5854 sloc) 181 KB
<?php
/*
* Textpattern Content Management System
* https://textpattern.com/
*
* Copyright (C) 2018 The Textpattern Development Team
*
* This file is part of Textpattern.
*
* Textpattern is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* Textpattern is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Textpattern. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Collection of miscellaneous tools.
*
* @package Misc
*/
/**
* Strips NULL bytes.
*
* @param string|array $in The input value
* @return mixed
*/
function deNull($in)
{
return is_array($in) ? doArray($in, 'deNull') : strtr($in, array("\0" => ''));
}
/**
* Strips carriage returns and linefeeds.
*
* @param string|array $in The input value
* @return mixed
*/
function deCRLF($in)
{
return is_array($in) ? doArray($in, 'deCRLF') : strtr($in, array(
"\n" => '',
"\r" => '',
));
}
/**
* Applies a callback to a given string or an array.
*
* @param string|array $in An array or a string to run through the callback function
* @param callback $function The callback function
* @return mixed
* @example
* echo doArray(array('value1', 'value2'), 'intval');
*/
function doArray($in, $function)
{
if (is_array($in)) {
return array_map($function, $in);
}
if (is_array($function)) {
return call_user_func($function, $in);
}
return $function($in);
}
/**
* Un-quotes a quoted string or an array of values.
*
* @param string|array $in The input value
* @return mixed
*/
function doStrip($in)
{
return is_array($in) ? doArray($in, 'doStrip') : doArray($in, 'stripslashes');
}
/**
* Strips HTML and PHP tags from a string or an array.
*
* @param string|array $in The input value
* @return mixed
* @example
* echo doStripTags('<p>Hello world!</p>');
*/
function doStripTags($in)
{
return is_array($in) ? doArray($in, 'doStripTags') : doArray($in, 'strip_tags');
}
/**
* Converts entity escaped brackets back to characters.
*
* @param string|array $in The input value
* @return mixed
*/
function doDeEnt($in)
{
return doArray($in, 'deEntBrackets');
}
/**
* Converts entity escaped brackets back to characters.
*
* @param string $in The input value
* @return string
*/
function deEntBrackets($in)
{
$array = array(
'&#60;' => '<',
'&lt;' => '<',
'&#x3C;' => '<',
'&#62;' => '>',
'&gt;' => '>',
'&#x3E;' => '>',
);
foreach ($array as $k => $v) {
$in = preg_replace("/".preg_quote($k)."/i", $v, $in);
}
return $in;
}
/**
* Escapes special characters for use in an SQL statement.
*
* Always use this function when dealing with user-defined values in SQL
* statements. If this function is not used to escape user-defined data in a
* statement, the query is vulnerable to SQL injection attacks.
*
* @param string|array $in The input value
* @return mixed An array of escaped values or a string depending on $in
* @package DB
* @example
* echo safe_field('column', 'table', "color = '" . doSlash(gps('color')) . "'");
*/
function doSlash($in)
{
return doArray($in, 'safe_escape');
}
/**
* Escape SQL LIKE pattern's wildcards for use in an SQL statement.
*
* @param string|array $in The input value
* @return mixed An array of escaped values or a string depending on $in
* @since 4.6.0
* @package DB
* @example
* echo safe_field('column', 'table', "color LIKE '" . doLike(gps('color')) . "'");
*/
function doLike($in)
{
return doArray($in, 'safe_escape_like');
}
/**
* A shell for htmlspecialchars() with $flags defaulting to ENT_QUOTES.
*
* @param string $string The string being converted
* @param int $flags A bitmask of one or more flags. The default is ENT_QUOTES
* @param string $encoding Defines encoding used in conversion. The default is UTF-8
* @param bool $double_encode When double_encode is turned off PHP will not encode existing HTML entities, the default is to convert everything
* @return string
* @see https://secure.php.net/manual/en/function.htmlspecialchars.php
* @since 4.5.0
* @package Filter
*/
function txpspecialchars($string, $flags = ENT_QUOTES, $encoding = 'UTF-8', $double_encode = true)
{
// Ignore ENT_HTML5 and ENT_XHTML for now.
// ENT_HTML5 and ENT_XHTML are defined in PHP 5.4+ but we consistently encode single quotes as &#039; in any doctype.
// global $prefs;
// static $h5 = null;
//
// if (defined(ENT_HTML5)) {
// if ($h5 === null) {
// $h5 = ($prefs['doctype'] == 'html5' && txpinterface == 'public');
// }
//
// if ($h5) {
// $flags = ($flags | ENT_HTML5) & ~ENT_HTML401;
// }
// }
//
return htmlspecialchars($string, $flags, $encoding, $double_encode);
}
/**
* Converts special characters to HTML entities.
*
* @param array|string $in The input value
* @return mixed The array or string with HTML syntax characters escaped
* @package Filter
*/
function doSpecial($in)
{
return doArray($in, 'txpspecialchars');
}
/**
* Converts the given value to NULL.
*
* @param mixed $a The input value
* @return null
* @package Filter
* @access private
*/
function _null($a)
{
return null;
}
/**
* Converts an array of values to NULL.
*
* @param array $in The array
* @return array
* @package Filter
*/
function array_null($in)
{
return array_map('_null', $in);
}
/**
* Escapes a page title. Converts &lt;, &gt;, ', " characters to HTML entities.
*
* @param string $title The input string
* @return string The string escaped
* @package Filter
*/
function escape_title($title)
{
return strtr($title, array(
'<' => '&#60;',
'>' => '&#62;',
"'" => '&#39;',
'"' => '&#34;',
));
}
/**
* Sanitises a string for use in a JavaScript string.
*
* Escapes \, \n, \r, " and ' characters. It removes 'PARAGRAPH SEPARATOR'
* (U+2029) and 'LINE SEPARATOR' (U+2028). When you need to pass a string
* from PHP to JavaScript, use this function to sanitise the value to avoid
* XSS attempts.
*
* @param string $js JavaScript input
* @return string Escaped JavaScript
* @since 4.4.0
* @package Filter
*/
function escape_js($js)
{
$js = preg_replace('/[\x{2028}\x{2029}]/u', '', $js);
return addcslashes($js, "\\\'\"\n\r");
}
/**
* Escapes CDATA section for an XML document.
*
* @param string $str The string
* @return string XML representation wrapped in CDATA tags
* @package XML
*/
function escape_cdata($str)
{
return '<![CDATA['.str_replace(']]>', ']]]><![CDATA[]>', $str).']]>';
}
/**
* Returns a localisation string.
*
* @param string $var String name
* @param array $atts Replacement pairs
* @param string $escape Convert special characters to HTML entities. Either "html" or ""
* @return string A localisation string
* @package L10n
*/
function gTxt($var, $atts = array(), $escape = 'html')
{
static $txpLang = null;
if ($txpLang === null) {
$txpLang = Txp::get('\Textpattern\L10n\Lang');
}
return $txpLang->txt($var, $atts, $escape);
}
/**
* Loads client-side localisation scripts.
*
* Passes localisation strings from the database to JavaScript.
*
* Only works on the admin-side pages.
*
* @param string|array $var Scalar or array of string keys
* @param array $atts Array or array of arrays of variable substitution pairs
* @param array $route Optional events/steps upon which to add the strings
* @since 4.5.0
* @package L10n
* @example
* gTxtScript(array('string1', 'string2', 'string3'));
*/
function gTxtScript($var, $atts = array(), $route = array())
{
global $textarray_script, $event, $step;
$targetEvent = empty($route[0]) ? null : (array)$route[0];
$targetStep = empty($route[1]) ? null : (array)$route[1];
if (($targetEvent === null || in_array($event, $targetEvent)) && ($targetStep === null || in_array($step, $targetStep))) {
if (!is_array($textarray_script)) {
$textarray_script = array();
}
$data = is_array($var) ? array_map('gTxt', $var, $atts) : (array) gTxt($var, $atts);
$textarray_script = $textarray_script + array_combine((array) $var, $data);
}
}
/**
* Handle refreshing the passed AJAX content to the UI.
*
* @param array $partials Partials array
* @param array $rs Record set of the edited content
*/
function updatePartials($partials, $rs, $types)
{
if (!is_array($types)) {
$types = array($types);
}
foreach ($partials as $k => $p) {
if (in_array($p['mode'], $types)) {
$cb = $p['cb'];
$partials[$k]['html'] = (is_array($cb) ? call_user_func($cb, $rs, $k) : $cb($rs, $k));
}
}
return $partials;
}
/**
* Handle refreshing the passed AJAX content to the UI.
*
* @param array $partials Partials array
* @return array Response to send back to the browser
*/
function updateVolatilePartials($partials)
{
$response = array();
// Update the volatile partials.
foreach ($partials as $k => $p) {
// Volatile partials need a target DOM selector.
if (empty($p['selector']) && $p['mode'] != PARTIAL_STATIC) {
trigger_error(gTxt('empty_partial_selector', array('{name}' => $k)), E_USER_ERROR);
} else {
// Build response script.
list($selector, $fragment) = (array)$p['selector'] + array(null, null);
if ($p['mode'] == PARTIAL_VOLATILE) {
// Volatile partials replace *all* of the existing HTML
// fragment for their selector with the new one.
$selector = do_list($selector);
$fragment = isset($fragment) ? do_list($fragment) + $selector : $selector;
$response[] = 'var $html = $("<div>'.escape_js($p['html']).'</div>")';
foreach ($selector as $i => $sel) {
$response[] = '$("'.$sel.'").replaceWith($html.find("'.$fragment[$i].'"))';
}
} elseif ($p['mode'] == PARTIAL_VOLATILE_VALUE) {
// Volatile partial values replace the *value* of elements
// matching their selector.
$response[] = '$("'.$selector.'").val("'.escape_js($p['html']).'")';
}
}
}
return $response;
}
/**
* Returns given timestamp in a format of 01 Jan 2001 15:19:16.
*
* @param int $timestamp The UNIX timestamp
* @return string A formatted date
* @access private
* @see safe_stftime()
* @package DateTime
* @example
* echo gTime();
*/
function gTime($timestamp = 0)
{
return safe_strftime('%d&#160;%b&#160;%Y %X', $timestamp);
}
/**
* Creates a dumpfile from a backtrace and outputs given parameters.
*
* @package Debug
*/
function dmp()
{
static $f = false;
if (defined('txpdmpfile')) {
global $prefs;
if (!$f) {
$f = fopen($prefs['tempdir'].'/'.txpdmpfile, 'a');
}
$stack = get_caller();
fwrite($f, "\n[".$stack[0].t.safe_strftime('iso8601')."]\n");
}
$a = func_get_args();
if (!$f) {
echo "<pre dir=\"auto\">".n;
}
foreach ($a as $thing) {
$out = is_scalar($thing) ? strval($thing) : var_export($thing, true);
if ($f) {
fwrite($f, $out.n);
} else {
echo txpspecialchars($out).n;
}
}
if (!$f) {
echo "</pre>".n;
}
}
/**
* Gets the given language's strings from the database.
*
* Fetches the given language from the database and returns the strings
* as an array.
*
* If no $events is specified, only appropriate strings for the current context
* are returned. If 'txpinterface' constant equals 'admin' all strings are
* returned. Otherwise, only strings from events 'common' and 'public'.
*
* If $events is FALSE, returns all strings.
*
* @param string $lang The language code
* @param array|string|bool $events An array of loaded events
* @return array
* @package L10n
* @see load_lang_event()
* @example
* print_r(
* load_lang('en-gb', false)
* );
*/
function load_lang($lang, $events = null)
{
global $production_status, $event;
$textarray = Txp::get('\Textpattern\L10n\Lang')->load($lang, $events);
if (($production_status !== 'live' || $event === 'diag')
&& @$debug = parse_ini_file(txpath.DS.'mode.ini')
) {
$textarray += (array)$debug;
Txp::get('\Textpattern\L10n\Lang')->setPack($textarray);
}
return $textarray;
}
/**
* Loads date definitions from a localisation file.
*
* @param string $lang The language
* @package L10n
* @deprecated in 4.6.0
*/
function load_lang_dates($lang)
{
$filename = is_file(txpath.'/lang/'.$lang.'_dates.txt') ?
txpath.'/lang/'.$lang.'_dates.txt' :
txpath.'/lang/en-gb_dates.txt';
$file = @file(txpath.'/lang/'.$lang.'_dates.txt', 'r');
if (is_array($file)) {
foreach ($file as $line) {
if ($line[0] == '#' || strlen($line) < 2) {
continue;
}
list($name, $val) = explode('=>', $line, 2);
$out[trim($name)] = trim($val);
}
return $out;
}
return false;
}
/**
* Gets language strings for the given event.
*
* If no $lang is specified, the strings are loaded from the currently
* active language.
*
* @param string $event The event to get, e.g. "common", "admin", "public"
* @param string $lang The language code
* @return array|string Array of string on success, or an empty string when no strings were found
* @package L10n
* @see load_lang()
* @example
* print_r(
* load_lang_event('common')
* );
*/
function load_lang_event($event, $lang = LANG)
{
$installed = (false !== safe_field("name", 'txp_lang', "lang = '".doSlash($lang)."' LIMIT 1"));
$lang_code = ($installed) ? $lang : TEXTPATTERN_DEFAULT_LANG;
$rs = safe_rows_start("name, data", 'txp_lang', "lang = '".doSlash($lang_code)."' AND event = '".doSlash($event)."'");
$out = array();
if ($rs && !empty($rs)) {
while ($a = nextRow($rs)) {
$out[$a['name']] = $a['data'];
}
}
return ($out) ? $out : '';
}
/**
* Grants privileges to user-groups.
*
* Will not let you override existing privs.
*
* @param string $res The resource
* @param string $perm List of user-groups, e.g. '1,2,3'
* @package User
* @example
* add_privs('my_admin_side_panel_event', '1,2,3,4,5');
*/
function add_privs($res, $perm = '1')
{
global $txp_permissions;
if (!isset($txp_permissions[$res])) {
$perm = join(',', do_list_unique($perm));
$txp_permissions[$res] = $perm;
}
}
/**
* Checks if a user has privileges to the given resource.
*
* @param string $res The resource
* @param string $user The user. If no user name is supplied, assume the current logged in user
* @return bool
* @package User
* @example
* add_privs('my_privilege_resource', '1,2,3');
* if (has_privs('my_privilege_resource', 'username'))
* {
* echo "'username' has privileges to 'my_privilege_resource'.";
* }
*/
function has_privs($res, $user = '')
{
global $txp_user, $txp_permissions;
static $privs;
$user = (string) $user;
if ($user === '') {
$user = (string) $txp_user;
}
if ($user !== '') {
if (!isset($privs[$user])) {
$privs[$user] = safe_field("privs", 'txp_users', "name = '".doSlash($user)."'");
}
if (isset($txp_permissions[$res]) && $privs[$user] && $txp_permissions[$res]) {
return in_list($privs[$user], $txp_permissions[$res]);
}
}
return false;
}
/**
* Require privileges from a user to the given resource.
*
* Terminates the script if user doesn't have required privileges.
*
* @param string|null $res The resource, or NULL
* @param string $user The user. If no user name is supplied, assume the current logged in user
* @package User
* @example
* require_privs('article.edit');
*/
function require_privs($res = null, $user = '')
{
if ($res === null || !has_privs($res, $user)) {
pagetop(gTxt('restricted_area'));
echo graf(gTxt('restricted_area'), array('class' => 'restricted-area'));
end_page();
exit;
}
}
/**
* Gets a list of users having access to a resource.
*
* @param string $res The resource, e.g. 'article.edit.published'
* @return array A list of usernames
* @since 4.5.0
* @package User
*/
function the_privileged($res)
{
global $txp_permissions;
if (isset($txp_permissions[$res])) {
return safe_column("name", 'txp_users', "FIND_IN_SET(privs, '".$txp_permissions[$res]."') ORDER BY name ASC");
} else {
return array();
}
}
/**
* Gets a list of user groups.
*
* @return array
* @package User
* @example
* print_r(
* get_groups()
* );
*/
function get_groups()
{
global $txp_groups;
return doArray($txp_groups, 'gTxt');
}
/**
* Gets the dimensions of an image for a HTML &lt;img&gt; tag.
*
* @param string $name The filename
* @return string|bool height="100" width="40", or FALSE on failure
* @package Image
* @example
* if ($size = sizeImage('/path/to/image.png'))
* {
* echo "&lt;img src='image.png' {$size} /&gt;";
* }
*/
function sizeImage($name)
{
$size = @getimagesize($name);
return is_array($size) ? $size[3] : false;
}
/**
* Lists image types that can be safely uploaded.
*
* Returns different results based on the logged in user's privileges.
*
* @param int $type If set, validates the given value
* @return mixed
* @package Image
* @since 4.6.0
* @example
* list($width, $height, $extension) = getimagesize('image');
* if ($type = get_safe_image_types($extension))
* {
* echo "Valid image of {$type}.";
* }
*/
function get_safe_image_types($type = null)
{
if (!has_privs('image.create.trusted')) {
$extensions = array(0, '.gif', '.jpg', '.png');
} else {
$extensions = array(0, '.gif', '.jpg', '.png', '.swf', 0, 0, 0, 0, 0, 0, 0, 0, '.swf');
}
if (func_num_args() > 0) {
return !empty($extensions[$type]) ? $extensions[$type] : false;
}
return $extensions;
}
/**
* Checks if GD supports the given image type.
*
* @param string $image_type Either '.gif', '.jpg', '.png'
* @return bool TRUE if the type is supported
* @package Image
*/
function check_gd($image_type)
{
if (!function_exists('gd_info')) {
return false;
}
$gd_info = gd_info();
switch ($image_type) {
case '.gif':
return ($gd_info['GIF Create Support'] == true);
break;
case '.jpg':
return ($gd_info['JPEG Support'] == true);
break;
case '.png':
return ($gd_info['PNG Support'] == true);
break;
}
return false;
}
/**
* Uploads an image.
*
* Can be used to upload a new image or replace an existing one.
* If $id is specified, the image will be replaced. If $uploaded is set FALSE,
* $file can take a local file instead of HTTP file upload variable.
*
* All uploaded files will included on the Images panel.
*
* @param array $file HTTP file upload variables
* @param array $meta Image meta data, allowed keys 'caption', 'alt', 'category'
* @param int $id Existing image's ID
* @param bool $uploaded If FALSE, $file takes a filename instead of upload vars
* @return array|string An array of array(message, id) on success, localized error string on error
* @package Image
* @example
* print_r(image_data(
* $_FILES['myfile'],
* array(
* 'caption' => '',
* 'alt' => '',
* 'category' => '',
* )
* ));
*/
function image_data($file, $meta = array(), $id = 0, $uploaded = true)
{
global $txp_user, $event;
$name = $file['name'];
$error = $file['error'];
$file = $file['tmp_name'];
if ($uploaded) {
if ($error !== UPLOAD_ERR_OK) {
return upload_get_errormsg($error);
}
$file = get_uploaded_file($file);
}
if (empty($file)) {
return upload_get_errormsg(UPLOAD_ERR_NO_FILE);
}
if (get_pref('file_max_upload_size') < filesize($file)) {
unlink($file);
return upload_get_errormsg(UPLOAD_ERR_FORM_SIZE);
}
list($w, $h, $extension) = getimagesize($file);
$ext = get_safe_image_types($extension);
if (!$ext) {
return gTxt('only_graphic_files_allowed');
}
$name = substr($name, 0, strrpos($name, '.')).$ext;
$safename = doSlash($name);
$meta = lAtts(array(
'category' => '',
'caption' => '',
'alt' => '',
), (array) $meta, false);
extract(doSlash($meta));
$q = "
name = '$safename',
ext = '$ext',
w = $w,
h = $h,
alt = '$alt',
caption = '$caption',
category = '$category',
date = NOW(),
author = '".doSlash($txp_user)."'
";
if (empty($id)) {
$rs = safe_insert('txp_image', $q);
if ($rs) {
$id = $GLOBALS['ID'] = $rs;
} else {
return gTxt('image_save_error');
}
} else {
$id = assert_int($id);
}
$newpath = IMPATH.$id.$ext;
if (shift_uploaded_file($file, $newpath) == false) {
if (!empty($rs)) {
safe_delete('txp_image', "id = '$id'");
unset($GLOBALS['ID']);
}
return gTxt('directory_permissions', array('{path}' => $newpath));
} elseif (empty($rs)) {
$rs = safe_update('txp_image', $q, "id = $id");
if (!$rs) {
return gTxt('image_save_error');
}
}
@chmod($newpath, 0644);
// GD is supported
if (check_gd($ext)) {
// Auto-generate a thumbnail using the last settings
if (get_pref('thumb_w') > 0 || get_pref('thumb_h') > 0) {
$t = new txp_thumb($id);
$t->crop = (bool) get_pref('thumb_crop');
$t->hint = '0';
$t->width = (int) get_pref('thumb_w');
$t->height = (int) get_pref('thumb_h');
$t->write();
}
}
$message = gTxt('image_uploaded', array('{name}' => $name));
update_lastmod('image_uploaded', compact('id', 'name', 'ext', 'w', 'h', 'alt', 'caption', 'category', 'txpuser'));
// call post-upload plugins with new image's $id
callback_event('image_uploaded', $event, false, $id);
return array($message, $id);
}
/**
* Gets an image as an array.
*
* @param int $id image ID
* @param string $name image name
* @return array|bool An image data array, or FALSE on failure
* @package Image
* @example
* if ($image = imageFetchInfo($id))
* {
* print_r($image);
* }
*/
function imageFetchInfo($id = "", $name = "")
{
global $thisimage, $p;
static $cache = array();
if ($id) {
if (isset($cache['i'][$id])) {
return $cache['i'][$id];
} else {
$where = 'id = '.intval($id).' LIMIT 1';
}
} elseif ($name) {
if (isset($cache['n'][$name])) {
return $cache['n'][$name];
} else {
$where = "name = '".doSlash($name)."' LIMIT 1";
}
} elseif ($thisimage) {
$id = (int) $thisimage['id'];
return $cache['i'][$id] = $thisimage;
} elseif ($p) {
if (isset($cache['i'][$p])) {
return $cache['i'][$p];
} else {
$where = 'id = '.intval($p).' LIMIT 1';
}
} else {
assert_image();
return false;
}
$rs = safe_row("*", 'txp_image', $where);
if ($rs) {
$id = (int) $rs['id'];
return $cache['i'][$id] = image_format_info($rs);
} else {
trigger_error(gTxt('unknown_image'));
}
return false;
}
/**
* Formats image info.
*
* Takes an image data array generated by imageFetchInfo() and formats the contents.
*
* @param array $image The image
* @return array
* @see imageFetchInfo()
* @access private
* @package Image
*/
function image_format_info($image)
{
if (($unix_ts = @strtotime($image['date'])) > 0) {
$image['date'] = $unix_ts;
}
return $image;
}
/**
* Formats link info.
*
* @param array $link The link to format
* @return array Formatted link data
* @access private
* @package Link
*/
function link_format_info($link)
{
if (($unix_ts = @strtotime($link['date'])) > 0) {
$link['date'] = $unix_ts;
}
return $link;
}
/**
* Gets a HTTP GET or POST parameter.
*
* Internally handles and normalises MAGIC_QUOTES_GPC,
* strips CRLF from GET parameters and removes NULL bytes.
*
* @param string $thing The parameter to get
* @return string|array The value of $thing, or an empty string
* @package Network
* @example
* if (gps('sky') == 'blue' && gps('roses') == 'red')
* {
* echo 'Roses are red, sky is blue.';
* }
*/
function gps($thing, $default = '')
{
if (isset($_GET[$thing])) {
if (MAGIC_QUOTES_GPC) {
$out = doStrip($_GET[$thing]);
} else {
$out = $_GET[$thing];
}
$out = doArray($out, 'deCRLF');
} elseif (isset($_POST[$thing])) {
if (MAGIC_QUOTES_GPC) {
$out = doStrip($_POST[$thing]);
} else {
$out = $_POST[$thing];
}
} else {
$out = $default;
}
$out = doArray($out, 'deNull');
return $out;
}
/**
* Gets an array of HTTP GET or POST parameters.
*
* @param array $array The parameters to extract
* @return array
* @package Network
* @example
* extract(gpsa(array('sky', 'roses'));
* if ($sky == 'blue' && $roses == 'red')
* {
* echo 'Roses are red, sky is blue.';
* }
*/
function gpsa($array)
{
if (is_array($array)) {
$out = array();
foreach ($array as $a) {
$out[$a] = gps($a);
}
return $out;
}
return false;
}
/**
* Gets a HTTP POST parameter.
*
* Internally handles and normalises MAGIC_QUOTES_GPC,
* and removes NULL bytes.
*
* @param string $thing The parameter to get
* @return string|array The value of $thing, or an empty string
* @package Network
* @example
* if (ps('sky') == 'blue' && ps('roses') == 'red')
* {
* echo 'Roses are red, sky is blue.';
* }
*/
function ps($thing)
{
$out = '';
if (isset($_POST[$thing])) {
if (MAGIC_QUOTES_GPC) {
$out = doStrip($_POST[$thing]);
} else {
$out = $_POST[$thing];
}
}
$out = doArray($out, 'deNull');
return $out;
}
/**
* Gets an array of HTTP POST parameters.
*
* @param array $array The parameters to extract
* @return array
* @package Network
* @example
* extract(psa(array('sky', 'roses'));
* if ($sky == 'blue' && $roses == 'red')
* {
* echo 'Roses are red, sky is blue.';
* }
*/
function psa($array)
{
foreach ($array as $a) {
$out[$a] = ps($a);
}
return $out;
}
/**
* Gets an array of HTTP POST parameters and strips HTML and PHP tags
* from values.
*
* @param array $array The parameters to extract
* @return array
* @package Network
*/
function psas($array)
{
foreach ($array as $a) {
$out[$a] = doStripTags(ps($a));
}
return $out;
}
/**
* Gets all received HTTP POST parameters.
*
* @return array
* @package Network
*/
function stripPost()
{
if (isset($_POST)) {
if (MAGIC_QUOTES_GPC) {
return doStrip($_POST);
} else {
return $_POST;
}
}
return '';
}
/**
* Gets a variable from $_SERVER global array.
*
* @param mixed $thing The variable
* @return mixed The variable, or an empty string on error
* @package System
* @example
* echo serverSet('HTTP_USER_AGENT');
*/
function serverSet($thing)
{
return (isset($_SERVER[$thing])) ? $_SERVER[$thing] : '';
}
/**
* Gets the client's IP address.
*
* Supports proxies and uses 'X_FORWARDED_FOR' HTTP header if deemed necessary.
*
* @return string
* @package Network
* @example
* if ($ip = remote_addr())
* {
* echo "Your IP address is: {$ip}.";
* }
*/
function remote_addr()
{
$ip = serverSet('REMOTE_ADDR');
if (($ip == '127.0.0.1' || $ip == '::1' || $ip == '::ffff:127.0.0.1' || $ip == serverSet('SERVER_ADDR')) && serverSet('HTTP_X_FORWARDED_FOR')) {
$ips = explode(', ', serverSet('HTTP_X_FORWARDED_FOR'));
$ip = $ips[0];
}
return $ip;
}
/**
* Gets a variable from HTTP POST or a prefixed cookie.
*
* Fetches either a HTTP cookie of the given name prefixed with
* 'txp_', or a HTTP POST parameter without a prefix.
*
* @param string $thing The variable
* @return array|string The variable or an empty string
* @package Network
* @example
* if ($cs = psc('myVariable'))
* {
* echo "'txp_myVariable' cookie or 'myVariable' POST parameter contained: '{$cs}'.";
* }
*/
function pcs($thing)
{
if (isset($_COOKIE["txp_".$thing])) {
if (MAGIC_QUOTES_GPC) {
return doStrip($_COOKIE["txp_".$thing]);
} else {
return $_COOKIE["txp_".$thing];
}
} elseif (isset($_POST[$thing])) {
if (MAGIC_QUOTES_GPC) {
return doStrip($_POST[$thing]);
} else {
return $_POST[$thing];
}
}
return '';
}
/**
* Gets a HTTP cookie.
*
* Internally normalises MAGIC_QUOTES_GPC.
*
* @param string $thing The cookie
* @return string The cookie or an empty string
* @package Network
* @example
* if ($cs = cs('myVariable'))
* {
* echo "'myVariable' cookie contained: '{$cs}'.";
* }
*/
function cs($thing)
{
if (isset($_COOKIE[$thing])) {
if (MAGIC_QUOTES_GPC) {
return doStrip($_COOKIE[$thing]);
} else {
return $_COOKIE[$thing];
}
}
return '';
}
/**
* Converts a boolean to a localised "Yes" or "No" string.
*
* @param bool $status The boolean. Ignores type and as such can also take a string or an integer
* @return string No if FALSE, Yes otherwise
* @package L10n
* @example
* echo yes_no(3 * 3 === 2);
*/
function yes_no($status)
{
return ($status) ? gTxt('yes') : gTxt('no');
}
/**
* Gets UNIX timestamp with microseconds.
*
* @return float
* @package DateTime
* @example
* echo getmicrotime();
*/
function getmicrotime()
{
list($usec, $sec) = explode(" ", microtime());
return ((float) $usec + (float) $sec);
}
/**
* Loads the given plugin or checks if it was loaded.
*
* @param string $name The plugin
* @param bool $force If TRUE loads the plugin even if it's disabled
* @return bool TRUE if the plugin is loaded
* @example
* if (load_plugin('abc_plugin'))
* {
* echo "'abc_plugin' is active.";
* }
*/
function load_plugin($name, $force = false)
{
global $plugins, $plugins_ver, $prefs, $txp_current_plugin;
if (is_array($plugins) and in_array($name, $plugins)) {
return true;
}
if (!empty($prefs['plugin_cache_dir'])) {
$dir = rtrim($prefs['plugin_cache_dir'], '/').'/';
// In case it's a relative path.
if (!is_dir($dir)) {
$dir = rtrim(realpath(txpath.'/'.$dir), '/').'/';
}
if (is_file($dir.$name.'.php')) {
$plugins[] = $name;
set_error_handler("pluginErrorHandler");
if (isset($txp_current_plugin)) {
$txp_parent_plugin = $txp_current_plugin;
}
$txp_current_plugin = $name;
include $dir.$name.'.php';
$txp_current_plugin = isset($txp_parent_plugin) ? $txp_parent_plugin : null;
$plugins_ver[$name] = @$plugin['version'];
if (isset($plugin['textpack'])) {
$strings = array();
$pack = Txp::get('\Textpattern\Textpack\Parser');
$pack->parse($plugin['textpack']);
$useLang = txpinterface === 'admin' ? get_pref('language_ui', TEXTPATTERN_DEFAULT_LANG) : get_pref('language', TEXTPATTERN_DEFAULT_LANG);
$wholePack = $pack->getStrings($useLang);
if (!$wholePack) {
$wholePack = $pack->getStrings(TEXTPATTERN_DEFAULT_LANG);
}
foreach ($wholePack as $entry) {
$strings[$entry['name']] = $entry['data'];
}
// Append lang strings on-the-fly.
Txp::get('\Textpattern\L10n\Lang')->setPack($strings, true);
}
restore_error_handler();
return true;
}
}
$rs = safe_row("name, code, version", 'txp_plugin', ($force ? '' : "status = 1 AND ")."name = '".doSlash($name)."'");
if ($rs) {
$plugins[] = $rs['name'];
$plugins_ver[$rs['name']] = $rs['version'];
set_error_handler("pluginErrorHandler");
if (isset($txp_current_plugin)) {
$txp_parent_plugin = $txp_current_plugin;
}
$txp_current_plugin = $rs['name'];
eval($rs['code']);
$txp_current_plugin = isset($txp_parent_plugin) ? $txp_parent_plugin : null;
restore_error_handler();
return true;
}
return false;
}
/**
* Loads a plugin.
*
* Identical to load_plugin() except upon failure it issues an E_USER_ERROR.
*
* @param string $name The plugin
* @return bool
* @see load_plugin()
*/
function require_plugin($name)
{
if (!load_plugin($name)) {
trigger_error(gTxt('plugin_include_error', array('{name}' => $name)), E_USER_ERROR);
return false;
}
return true;
}
/**
* Loads a plugin.
*
* Identical to load_plugin() except upon failure it issues an E_USER_WARNING.
*
* @param string $name The plugin
* @return bool
* @see load_plugin()
*/
function include_plugin($name)
{
if (!load_plugin($name)) {
trigger_error(gTxt('plugin_include_error', array('{name}' => $name)), E_USER_WARNING);
return false;
}
return true;
}
/**
* Error handler for plugins.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @access private
* @package Debug
*/
function pluginErrorHandler($errno, $errstr, $errfile, $errline)
{
global $production_status, $txp_current_plugin;
$error = array();
if ($production_status == 'testing') {
$error = array(
E_WARNING => 'Warning',
E_RECOVERABLE_ERROR => 'Catchable fatal error',
E_USER_ERROR => 'User_Error',
E_USER_WARNING => 'User_Warning',
);
} elseif ($production_status == 'debug') {
$error = array(
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_RECOVERABLE_ERROR => 'Catchable fatal error',
E_USER_ERROR => 'User_Error',
E_USER_WARNING => 'User_Warning',
E_USER_NOTICE => 'User_Notice',
);
if (!isset($error[$errno])) {
$error[$errno] = $errno;
}
}
if (!isset($error[$errno]) || !error_reporting()) {
return;
}
printf(
'<pre dir="auto">'.gTxt('plugin_load_error').' <b>%s</b> -> <b>%s: %s on line %s</b></pre>',
$txp_current_plugin,
$error[$errno],
$errstr,
$errline
);
if ($production_status == 'debug') {
print "\n<pre class=\"backtrace\" dir=\"ltr\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>";
}
}
/**
* Error handler for page templates.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @access private
* @package Debug
*/
function tagErrorHandler($errno, $errstr, $errfile, $errline)
{
global $production_status, $txp_current_tag, $txp_current_form, $pretext, $trace;
$error = array();
if ($production_status == 'testing') {
$error = array(
E_WARNING => 'Warning',
E_RECOVERABLE_ERROR => 'Textpattern Catchable fatal error',
E_USER_ERROR => 'Textpattern Error',
E_USER_WARNING => 'Textpattern Warning',
);
} elseif ($production_status == 'debug') {
$error = array(
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_RECOVERABLE_ERROR => 'Textpattern Catchable fatal error',
E_USER_ERROR => 'Textpattern Error',
E_USER_WARNING => 'Textpattern Warning',
E_USER_NOTICE => 'Textpattern Notice',
);
if (!isset($error[$errno])) {
$error[$errno] = $errno;
}
}
if (!isset($error[$errno]) || !error_reporting()) {
return;
}
if (empty($pretext['page'])) {
$page = gTxt('none');
} else {
$page = $pretext['page'];
}
if (!isset($txp_current_form)) {
$txp_current_form = gTxt('none');
}
$locus = gTxt('while_parsing_page_form', array(
'{page}' => $page,
'{form}' => $txp_current_form,
));
printf(
"<pre dir=\"auto\">".gTxt('tag_error').' <b>%s</b> -> <b> %s: %s %s</b></pre>',
txpspecialchars($txp_current_tag),
$error[$errno],
$errstr,
$locus
);
if ($production_status == 'debug') {
print "\n<pre class=\"backtrace\" dir=\"ltr\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>";
$trace->log(gTxt('tag_error').' '.$txp_current_tag.' -> '.$error[$errno].': '.$errstr.' '.$locus);
}
}
/**
* Error handler for XML feeds.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @access private
* @package Debug
*/
function feedErrorHandler($errno, $errstr, $errfile, $errline)
{
global $production_status;
if ($production_status != 'debug') {
return;
}
return tagErrorHandler($errno, $errstr, $errfile, $errline);
}
/**
* Error handler for admin-side pages.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @access private
* @package Debug
*/
function adminErrorHandler($errno, $errstr, $errfile, $errline)
{
global $production_status, $theme, $event, $step;
$error = array();
if ($production_status == 'testing') {
$error = array(
E_WARNING => 'Warning',
E_RECOVERABLE_ERROR => 'Catchable fatal error',
E_USER_ERROR => 'User_Error',
E_USER_WARNING => 'User_Warning',
);
} elseif ($production_status == 'debug') {
$error = array(
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_RECOVERABLE_ERROR => 'Catchable fatal error',
E_USER_ERROR => 'User_Error',
E_USER_WARNING => 'User_Warning',
E_USER_NOTICE => 'User_Notice',
);
if (!isset($error[$errno])) {
$error[$errno] = $errno;
}
}
if (!isset($error[$errno]) || !error_reporting()) {
return;
}
// When even a minimum environment is missing.
if (!isset($production_status)) {
echo '<pre dir="auto">'.gTxt('internal_error').' "'.$errstr.'"'.n."in $errfile at line $errline".'</pre>';
return;
}
$backtrace = '';
if (has_privs('debug.verbose')) {
$msg = $error[$errno].' "'.$errstr.'"';
} else {
$msg = gTxt('internal_error');
}
if ($production_status == 'debug' && has_privs('debug.backtrace')) {
$msg .= n."in $errfile at line $errline";
$backtrace = join(n, get_caller(10, 1));
}
if ($errno == E_ERROR || $errno == E_USER_ERROR) {
$httpstatus = 500;
} else {
$httpstatus = 200;
}
$out = "$msg.\n$backtrace";
if (http_accept_format('html')) {
if ($backtrace) {
echo "<pre dir=\"auto\">$msg.</pre>".
n.'<pre class="backtrace" dir="ltr"><code>'.
txpspecialchars($backtrace).'</code></pre>';
} elseif (is_object($theme)) {
echo $theme->announce(array($out, E_ERROR), true);
} else {
echo "<pre dir=\"auto\">$out</pre>";
}
} elseif (http_accept_format('js')) {
if (is_object($theme)) {
send_script_response($theme->announce_async(array($out, E_ERROR), true));
} else {
send_script_response('/* '.$out.'*/');
}
} elseif (http_accept_format('xml')) {
send_xml_response(array(
'http-status' => $httpstatus,
'internal_error' => "$out",
));
} else {
txp_die($msg, 500);
}
}
/**
* Error handler for update scripts.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @access private
* @package Debug
*/
function updateErrorHandler($errno, $errstr, $errfile, $errline)
{
global $production_status;
$old = $production_status;
$production_status = 'debug';
adminErrorHandler($errno, $errstr, $errfile, $errline);
$production_status = $old;
throw new Exception('update failed');
}
/**
* Error handler for public-side.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @access private
* @package Debug
*/
function publicErrorHandler($errno, $errstr, $errfile, $errline)
{
global $production_status;
$error = array();
if ($production_status == 'testing') {
$error = array(
E_WARNING => 'Warning',
E_USER_ERROR => 'Textpattern Error',
E_USER_WARNING => 'Textpattern Warning',
);
} elseif ($production_status == 'debug') {
$error = array(
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_USER_ERROR => 'Textpattern Error',
E_USER_WARNING => 'Textpattern Warning',
E_USER_NOTICE => 'Textpattern Notice',
);
if (!isset($error[$errno])) {
$error[$errno] = $errno;
}
}
if (!isset($error[$errno]) || !error_reporting()) {
return;
}
printf(
"<pre dir=\"auto\">".gTxt('general_error').' <b>%s: %s on line %s</b></pre>',
$error[$errno],
$errstr,
$errline
);
if ($production_status == 'debug') {
print "\n<pre class=\"backtrace\" dir=\"ltr\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>";
}
}
/**
* Loads plugins.
*
* @param bool $type If TRUE loads admin-side plugins, otherwise public
*/
function load_plugins($type = false)
{
global $prefs, $plugins, $plugins_ver, $app_mode, $trace;
if (!is_array($plugins)) {
$plugins = array();
}
$trace->start('[Loading plugins]');
if (!empty($prefs['plugin_cache_dir'])) {
$dir = rtrim($prefs['plugin_cache_dir'], '/').'/';
// In case it's a relative path.
if (!is_dir($dir)) {
$dir = rtrim(realpath(txpath.'/'.$dir), '/').'/';
}
$files = glob($dir.'*.php');
if ($files) {
natsort($files);
foreach ($files as $f) {
$trace->start("[Loading plugin from cache dir: '$f']");
load_plugin(basename($f, '.php'));
$trace->stop();
}
}
}
$admin = ($app_mode == 'async' ? '4,5' : '1,3,4,5');
$where = "status = 1 AND type IN (".($type ? $admin : "0,1,5").")";
$rs = safe_rows("name, code, version", 'txp_plugin', $where." ORDER BY load_order ASC, name ASC");
if ($rs) {
$old_error_handler = set_error_handler("pluginErrorHandler");
foreach ($rs as $a) {
if (!in_array($a['name'], $plugins)) {
$plugins[] = $a['name'];
$plugins_ver[$a['name']] = $a['version'];
$GLOBALS['txp_current_plugin'] = $a['name'];
$trace->start("[Loading plugin: '{$a['name']}' version '{$a['version']}']");
$eval_ok = eval($a['code']);
$trace->stop();
if ($eval_ok === false) {
echo gTxt('plugin_load_error_above').strong($a['name']).n.br;
}
unset($GLOBALS['txp_current_plugin']);
}
}
restore_error_handler();
}
$trace->stop();
}
/**
* Attachs a handler to a callback event.
*
* @param callback $func The callback function
* @param string $event The callback event
* @param string $step The callback step
* @param bool $pre Before or after. Works only with selected callback events
* @package Callback
* @example
* register_callback('my_callback_function', 'article.updated');
* function my_callback_function($event)
* {
* return "'$event' fired.";
* }
*/
function register_callback($func, $event, $step = '', $pre = 0)
{
global $plugin_callback;
$plugin_callback[] = array(
'function' => $func,
'event' => $event,
'step' => $step,
'pre' => $pre,
);
}
/**
* Registers an admin-side extension page.
*
* For now this just does the same as register_callback().
*
* @param callback $func The callback function
* @param string $event The callback event
* @param string $step The callback step
* @param bool $top The top or the bottom of the page
* @access private
* @see register_callback()
* @package Callback
*/
function register_page_extension($func, $event, $step = '', $top = 0)
{
register_callback($func, $event, $step, $top);
}
/**
* Call an event's callback.
*
* Executes all callback handlers attached to the matched event and step.
*
* When called, any event handlers attached with register_callback() to the
* matching event, step and pre will be called. The handlers, callback
* functions, will be executed in the same order they were registered.
*
* Any extra arguments will be passed to the callback handlers in the same
* argument position. This allows passing any type of data to the attached
* handlers. Callback handlers will also receive the event and the step.
*
* Returns a combined value of all values returned by the callback handlers.
*
* @param string $event The callback event
* @param string $step Additional callback step
* @param bool|int|array $pre Allows two callbacks, a prepending and an appending, with same event and step. Array allows return values chaining
* @return mixed The value returned by the attached callback functions, or an empty string
* @package Callback
* @see register_callback()
* @example
* register_callback('my_callback_function', 'my_custom_event');
* function my_callback_function($event, $step, $extra)
* {
* return "Passed '$extra' on '$event'.";
* }
* echo callback_event('my_custom_event', '', 0, 'myExtraValue');
*/
function callback_event($event, $step = '', $pre = 0)
{
global $plugin_callback, $production_status, $trace;
if (!is_array($plugin_callback)) {
return '';
}
list($pre, $renew) = (array)$pre + array(0, null);
$trace->start("[Callback_event: '$event', step='$step', pre='$pre']");
// Any payload parameters?
$argv = func_get_args();
$argv = (count($argv) > 3) ? array_slice($argv, 3) : array();
foreach ($plugin_callback as $c) {
if ($c['event'] == $event && (empty($c['step']) || $c['step'] == $step) && $c['pre'] == $pre) {
if (is_callable($c['function'])) {
if ($production_status !== 'live') {
$trace->start("\t[Call function: '".callback_tostring($c['function'])."'".(empty($argv) ? '' : ", argv='".serialize($argv)."'")."]");
}
$return_value = call_user_func_array($c['function'], array(
'event' => $event,
'step' => $step,
) + $argv);
if (isset($renew)) {
$argv[$renew] = $return_value;
}
if (isset($out) && !isset($renew)) {
if (is_array($return_value) && is_array($out)) {
$out = array_merge($out, $return_value);
} elseif (is_bool($return_value) && is_bool($out)) {
$out = $return_value && $out;
} else {
$out .= $return_value;
}
} else {
$out = $return_value;
}
if ($production_status !== 'live') {
$trace->stop();
}
} elseif ($production_status === 'debug') {
trigger_error(gTxt('unknown_callback_function', array('{function}' => callback_tostring($c['function']))), E_USER_WARNING);
}
}
}
$trace->stop();
if (isset($out)) {
return $out;
}
return '';
}
/**
* Call an event's callback with two optional byref parameters.
*
* @param string $event The callback event
* @param string $step Optional callback step
* @param bool $pre Allows two callbacks, a prepending and an appending, with same event and step
* @param mixed $data Optional arguments for event handlers
* @param mixed $options Optional arguments for event handlers
* @return array Collection of return values from event handlers
* @since 4.5.0
* @package Callback
*/
function callback_event_ref($event, $step = '', $pre = 0, &$data = null, &$options = null)
{
global $plugin_callback, $production_status;
if (!is_array($plugin_callback)) {
return array();
}
$return_value = array();
foreach ($plugin_callback as $c) {
if ($c['event'] == $event and (empty($c['step']) or $c['step'] == $step) and $c['pre'] == $pre) {
if (is_callable($c['function'])) {
// Cannot call event handler via call_user_func() as this would
// dereference all arguments. Side effect: callback handler
// *must* be ordinary function, *must not* be class method in
// PHP <5.4. See https://bugs.php.net/bug.php?id=47160.
$return_value[] = $c['function']($event, $step, $data, $options);
} elseif ($production_status == 'debug') {
trigger_error(gTxt('unknown_callback_function', array('{function}' => callback_tostring($c['function']))), E_USER_WARNING);
}
}
}
return $return_value;
}
/**
* Converts a callable to a string presentation.
*
* <code>
* echo callback_tostring(array('class', 'method'));
* </code>
*
* @param callback $callback The callback
* @return string The $callback as a human-readable string
* @since 4.5.0
* @package Callback
* @deprecated in 4.6.0
* @see \Textpattern\Type\Callable::toString()
*/
function callback_tostring($callback)
{
return Txp::get('\Textpattern\Type\TypeCallable', $callback)->toString();
}
/**
* Checks if a callback event has active handlers.
*
* @param string $event The callback event
* @param string $step The callback step
* @param bool $pre The position
* @return bool TRUE if the event is active, FALSE otherwise
* @since 4.6.0
* @package Callback
* @example
* if (has_handler('article_saved'))
* {
* echo "There are active handlers for 'article_saved' event.";
* }
*/
function has_handler($event, $step = '', $pre = 0)
{
return (bool) callback_handlers($event, $step, $pre, false);
}
/**
* Lists handlers attached to an event.
*
* @param string $event The callback event
* @param string $step The callback step
* @param bool $pre The position
* @param bool $as_string Return callables in string representation
* @return array|bool An array of handlers, or FALSE
* @since 4.6.0
* @package Callback
* @example
* if ($handlers = callback_handlers('article_saved'))
* {
* print_r($handlers);
* }
*/
function callback_handlers($event, $step = '', $pre = 0, $as_string = true)
{
global $plugin_callback;
$out = array();
foreach ((array) $plugin_callback as $c) {
if ($c['event'] == $event && (!$c['step'] || $c['step'] == $step) && $c['pre'] == $pre) {
if ($as_string) {
$out[] = callback_tostring($c['function']);
} else {
$out[] = $c['function'];
}
}
}
if ($out) {
return $out;
}
return false;
}
/**
* Registers a new admin-side panel and adds a navigation link to the menu.
*
* @param string $area The menu the panel appears in, e.g. "home", "content", "presentation", "admin", "extensions"
* @param string $panel The panel's event
* @param string $title The menu item's label
* @package Callback
* @example
* add_privs('abc_admin_event', '1,2');
* register_tab('extensions', 'abc_admin_event', 'My Panel');
* register_callback('abc_admin_function', 'abc_admin_event');
*/
function register_tab($area, $panel, $title)
{
global $plugin_areas, $event;
if ($event !== 'plugin') {
$plugin_areas[$area][$title] = $panel;
}
}
/**
* Call an event's pluggable UI function.
*
* @param string $event The event
* @param string $element The element selector
* @param string $default The default interface markup
* @return mixed Returned value from a callback handler, or $default if no custom UI was provided
* @package Callback
*/
function pluggable_ui($event, $element, $default = '')
{
$argv = func_get_args();
$argv = array_slice($argv, 2);
// Custom user interface, anyone?
// Signature for called functions:
// string my_called_func(string $event, string $step, string $default_markup[, mixed $context_data...])
$ui = call_user_func_array('callback_event', array(
'event' => $event,
'step' => $element,
'pre' => (string) $default === '' ? 0 : array(0, 0),
) + $argv);
// Either plugins provided a user interface, or we render our own.
return ($ui === '') ? $default : $ui;
}
/**
* Merge the second array into the first array.
*
* @param array $pairs The first array
* @param array $atts The second array
* @param bool $warn If TRUE triggers errors if second array contains values that are not in the first
* @return array The two arrays merged
* @package TagParser
*/
function lAtts($pairs, $atts, $warn = true)
{
global $pretext, $production_status, $txp_atts;
static $globals = null, $global_atts;
if ($globals === null) {
$global_atts = Txp::get('\Textpattern\Tag\Registry')->getRegistered(true);
$globals = array_filter($global_atts);
}
if (empty($pretext['_txp_atts'])) {
foreach ($atts as $name => $value) {
if (array_key_exists($name, $pairs)) {
if ($pairs[$name] !== null) {
unset($txp_atts[$name]);
}
$pairs[$name] = $value;
} elseif ($warn && $production_status !== 'live' && !array_key_exists($name, $global_atts)) {
trigger_error(gTxt('unknown_attribute', array('{att}' => $name)));
}
}
} else { // don't import unset globals
foreach ($atts as $name => $value) {
if (array_key_exists($name, $pairs) && (!isset($globals[$name]) || isset($txp_atts[$name]))) {
$pairs[$name] = $value;
unset($txp_atts[$name]);
}
}
}
return $pairs ? $pairs : false;
}
/**
* Sanitises a string for use in an article's URL title.
*
* @param string $text The title or an URL
* @param bool $force Force sanitisation
* @return string|null
* @package URL
*/
function stripSpace($text, $force = false)
{
if ($force || get_pref('attach_titles_to_permalinks')) {
$text = trim(sanitizeForUrl($text), '-');
if (get_pref('permlink_format')) {
return (function_exists('mb_strtolower') ? mb_strtolower($text, 'UTF-8') : strtolower($text));
} else {
return str_replace('-', '', $text);
}
}
}
/**
* Sanitises a string for use in a URL.
*
* Be aware that you still have to urlencode the string when appropriate.
* This function just makes the string look prettier and excludes some
* unwanted characters, but leaves UTF-8 letters and digits intact.
*
* @param string $text The string
* @return string
* @package URL
*/
function sanitizeForUrl($text)
{
$out = callback_event('sanitize_for_url', '', 0, $text);
if ($out !== '') {
return $out;
}
// Remove names entities and tags.
$text = preg_replace("/(^|&\S+;)|(<[^>]*>)/U", "", dumbDown($text));
// Remove all characters except letter, number, dash, space and backslash
$text = preg_replace('/[^\p{L}\p{N}\-_\s\/\\\\]/u', '', $text);
// Collapse spaces, minuses, (back-)slashes.
$text = trim(preg_replace('/[\s\-\/\\\\]+/', '-', $text), '-');
return $text;
}
/**
* Sanitises a string for use in a filename.
*
* @param string $text The string
* @return string
* @package File
*/
function sanitizeForFile($text)
{
$out = callback_event('sanitize_for_file', '', 0, $text);
if ($out !== '') {
return $out;
}
// Remove control characters and " * \ : < > ? / |
$text = preg_replace('/[\x00-\x1f\x22\x2a\x2f\x3a\x3c\x3e\x3f\x5c\x7c\x7f]+/', '', $text);
// Remove duplicate dots and any leading or trailing dots/spaces.
$text = preg_replace('/[.]{2,}/', '.', trim($text, '. '));
return $text;
}
/**
* Sanitises a string for use in a page template's name.
*
* @param string $text The string
* @return string
* @package Filter
* @access private
*/
function sanitizeForPage($text)
{
$out = callback_event('sanitize_for_page', '', 0, $text);
if ($out !== '') {
return $out;
}
return trim(preg_replace('/[<>&"\']/', '', $text));
}
/**
* Sanitises a string for use in a ORDER BY clause.
*
* @param string $text The string
* @return string
* @package Filter
* @access private
*/
function sanitizeForSort($text)
{
return trim(strtr($text, array('#' => ' ', '--' => ' ')));
}
/**
* Transliterates a string to ASCII.
*
* Used to generate RFC 3986 compliant and pretty ASCII-only URLs.
*
* @param string $str The string to convert
* @param string $lang The language which translation table is used
* @see sanitizeForUrl()
* @package L10n
*/
function dumbDown($str, $lang = null)
{
static $array;
if ($lang === null) {
$lang = get_pref('language_ui', LANG);
}
if (empty($array[$lang])) {
$array[$lang] = array( // Nasty, huh?
'&#192;' => 'A', '&Agrave;' => 'A', '&#193;' => 'A', '&Aacute;' => 'A', '&#194;' => 'A', '&Acirc;' => 'A',
'&#195;' => 'A', '&Atilde;' => 'A', '&#196;' => 'Ae', '&Auml;' => 'A', '&#197;' => 'A', '&Aring;' => 'A',
'&#198;' => 'Ae', '&AElig;' => 'AE',
'&#256;' => 'A', '&#260;' => 'A', '&#258;' => 'A',
'&#199;' => 'C', '&Ccedil;' => 'C', '&#262;' => 'C', '&#268;' => 'C', '&#264;' => 'C', '&#266;' => 'C',
'&#270;' => 'D', '&#272;' => 'D', '&#208;' => 'D', '&ETH;' => 'D',
'&#200;' => 'E', '&Egrave;' => 'E', '&#201;' => 'E', '&Eacute;' => 'E', '&#202;' => 'E', '&Ecirc;' => 'E', '&#203;' => 'E', '&Euml;' => 'E',
'&#274;' => 'E', '&#280;' => 'E', '&#282;' => 'E', '&#276;' => 'E', '&#278;' => 'E',
'&#284;' => 'G', '&#286;' => 'G', '&#288;' => 'G', '&#290;' => 'G',
'&#292;' => 'H', '&#294;' => 'H',
'&#204;' => 'I', '&Igrave;' => 'I', '&#205;' => 'I', '&Iacute;' => 'I', '&#206;' => 'I', '&Icirc;' => 'I', '&#207;' => 'I', '&Iuml;' => 'I',
'&#298;' => 'I', '&#296;' => 'I', '&#300;' => 'I', '&#302;' => 'I', '&#304;' => 'I',
'&#306;' => 'IJ',
'&#308;' => 'J',
'&#310;' => 'K',
'&#321;' => 'K', '&#317;' => 'K', '&#313;' => 'K', '&#315;' => 'K', '&#319;' => 'K',
'&#209;' => 'N', '&Ntilde;' => 'N', '&#323;' => 'N', '&#327;' => 'N', '&#325;' => 'N', '&#330;' => 'N',
'&#210;' => 'O', '&Ograve;' => 'O', '&#211;' => 'O', '&Oacute;' => 'O', '&#212;' => 'O', '&Ocirc;' => 'O', '&#213;' => 'O', '&Otilde;' => 'O',
'&#214;' => 'Oe', '&Ouml;' => 'Oe',
'&#216;' => 'O', '&Oslash;' => 'O', '&#332;' => 'O', '&#336;' => 'O', '&#334;' => 'O',
'&#338;' => 'OE',
'&#340;' => 'R', '&#344;' => 'R', '&#342;' => 'R',
'&#346;' => 'S', '&#352;' => 'S', '&#350;' => 'S', '&#348;' => 'S', '&#536;' => 'S',
'&#356;' => 'T', '&#354;' => 'T', '&#358;' => 'T', '&#538;' => 'T',
'&#217;' => 'U', '&Ugrave;' => 'U', '&#218;' => 'U', '&Uacute;' => 'U', '&#219;' => 'U', '&Ucirc;' => 'U',
'&#220;' => 'Ue', '&#362;' => 'U', '&Uuml;' => 'Ue',
'&#366;' => 'U', '&#368;' => 'U', '&#364;' => 'U', '&#360;' => 'U', '&#370;' => 'U',
'&#372;' => 'W',
'&#221;' => 'Y', '&Yacute;' => 'Y', '&#374;' => 'Y', '&#376;' => 'Y',
'&#377;' => 'Z', '&#381;' => 'Z', '&#379;' => 'Z',
'&#222;' => 'T', '&THORN;' => 'T',
'&#224;' => 'a', '&#225;' => 'a', '&#226;' => 'a', '&#227;' => 'a', '&#228;' => 'ae',
'&auml;' => 'ae',
'&#229;' => 'a', '&#257;' => 'a', '&#261;' => 'a', '&#259;' => 'a', '&aring;' => 'a',
'&#230;' => 'ae',
'&#231;' => 'c', '&#263;' => 'c', '&#269;' => 'c', '&#265;' => 'c', '&#267;' => 'c',
'&#271;' => 'd', '&#273;' => 'd', '&#240;' => 'd',
'&#232;' => 'e', '&#233;' => 'e', '&#234;' => 'e', '&#235;' => 'e', '&#275;' => 'e',
'&#281;' => 'e', '&#283;' => 'e', '&#277;' => 'e', '&#279;' => 'e',
'&#402;' => 'f',
'&#285;' => 'g', '&#287;' => 'g', '&#289;' => 'g', '&#291;' => 'g',
'&#293;' => 'h', '&#295;' => 'h',
'&#236;' => 'i', '&#237;' => 'i', '&#238;' => 'i', '&#239;' => 'i', '&#299;' => 'i',
'&#297;' => 'i', '&#301;' => 'i', '&#303;' => 'i', '&#305;' => 'i',
'&#307;' => 'ij',
'&#309;' => 'j',
'&#311;' => 'k', '&#312;' => 'k',
'&#322;' => 'l', '&#318;' => 'l', '&#314;' => 'l', '&#316;' => 'l', '&#320;' => 'l',
'&#241;' => 'n', '&#324;' => 'n', '&#328;' => 'n', '&#326;' => 'n', '&#329;' => 'n',
'&#331;' => 'n',
'&#242;' => 'o', '&#243;' => 'o', '&#244;' => 'o', '&#245;' => 'o', '&#246;' => 'oe',
'&ouml;' => 'oe',
'&#248;' => 'o', '&#333;' => 'o', '&#337;' => 'o', '&#335;' => 'o',
'&#339;' => 'oe',
'&#341;' => 'r', '&#345;' => 'r', '&#343;' => 'r',
'&#353;' => 's',
'&#249;' => 'u', '&#250;' => 'u', '&#251;' => 'u', '&#252;' => 'ue', '&#363;' => 'u',
'&uuml;' => 'ue',
'&#367;' => 'u', '&#369;' => 'u', '&#365;' => 'u', '&#361;' => 'u', '&#371;' => 'u',
'&#373;' => 'w',
'&#253;' => 'y', '&#255;' => 'y', '&#375;' => 'y',
'&#382;' => 'z', '&#380;' => 'z', '&#378;' => 'z',
'&#254;' => 't',
'&#223;' => 'ss',
'&#383;' => 'ss',
'&agrave;' => 'a', '&aacute;' => 'a', '&acirc;' => 'a', '&atilde;' => 'a', '&auml;' => 'ae',
'&aring;' => 'a', '&aelig;' => 'ae', '&ccedil;' => 'c', '&eth;' => 'd',
'&egrave;' => 'e', '&eacute;' => 'e', '&ecirc;' => 'e', '&euml;' => 'e',
'&igrave;' => 'i', '&iacute;' => 'i', '&icirc;' => 'i', '&iuml;' => 'i',
'&ntilde;' => 'n',
'&ograve;' => 'o', '&oacute;' => 'o', '&ocirc;' => 'o', '&otilde;' => 'o', '&ouml;' => 'oe',
'&oslash;' => 'o',
'&ugrave;' => 'u', '&uacute;' => 'u', '&ucirc;' => 'u', '&uuml;' => 'ue',
'&yacute;' => 'y', '&yuml;' => 'y',
'&thorn;' => 't',
'&szlig;' => 'ss',
);
if (is_file(txpath.'/lib/i18n-ascii.txt')) {
$i18n = parse_ini_file(txpath.'/lib/i18n-ascii.txt', true);
// Load the global map.
if (isset($i18n['default']) && is_array($i18n['default'])) {
$array[$lang] = array_merge($array[$lang], $i18n['default']);
// Base language overrides: 'de-AT' applies the 'de' section.
if (preg_match('/([a-zA-Z]+)-.+/', $lang, $m)) {
if (isset($i18n[$m[1]]) && is_array($i18n[$m[1]])) {
$array[$lang] = array_merge($array[$lang], $i18n[$m[1]]);
}
}
// Regional language overrides: 'de-AT' applies the 'de-AT' section.
if (isset($i18n[$lang]) && is_array($i18n[$lang])) {
$array[$lang] = array_merge($array[$lang], $i18n[$lang]);
}
}
// Load an old file (no sections) just in case.
else {
$array[$lang] = array_merge($array[$lang], $i18n);
}
}
}
return strtr($str, $array[$lang]);
}
/**
* Cleans a URL.
*
* @param string $url The URL
* @return string
* @access private
* @package URL
*/
function clean_url($url)
{
return preg_replace("/\"|'|(?:\s.*$)/", '', $url);
}
/**
* Replace the last space with a &#160; non-breaking space.
*
* @param string $str The string
* @return string
*/
function noWidow($str)
{
if (REGEXP_UTF8 == 1) {
return preg_replace('@[ ]+([[:punct:]]?[\p{L}\p{N}\p{Pc}]+[[:punct:]]?)$@u', '&#160;$1', rtrim($str));
}
return preg_replace('@[ ]+([[:punct:]]?\w+[[:punct:]]?)$@', '&#160;$1', rtrim($str));
}
/**
* Checks if an IP is on a spam blacklist.
*
* @param string $ip The IP address
* @param string|array $checks The checked lists. Defaults to 'spam_blacklists' preferences string
* @return string|bool The lists the IP is on or FALSE
* @package Comment
* @example
* if (is_blacklisted('127.0.0.1'))
* {
* echo "'127.0.0.1' is blacklisted.";
* }
*/
function is_blacklisted($ip, $checks = '')
{
if (!$checks) {
$checks = do_list_unique(get_pref('spam_blacklists'));
}
$rip = join('.', array_reverse(explode('.', $ip)));
foreach ((array) $checks as $a) {
$parts = explode(':', $a, 2);
$rbl = $parts[0];
if (isset($parts[1])) {
foreach (explode(':', $parts[1]) as $code) {
$codes[] = strpos($code, '.') ? $code : '127.0.0.'.$code;
}
}
$hosts = $rbl ? @gethostbynamel($rip.'.'.trim($rbl, '. ').'.') : false;
if ($hosts and (!isset($codes) or array_intersect($hosts, $codes))) {
$listed[] = $rbl;
}
}
return (!empty($listed)) ? join(', ', $listed) : false;
}
/**
* Checks if the user is authenticated on the public-side.
*
* @param string $user The checked username. If not provided, any user is accepted
* @return array|bool An array containing details about the user; name, RealName, email, privs. FALSE when the user hasn't authenticated.
* @package User
* @example
* if ($user = is_logged_in())
* {
* echo "Logged in as {$user['RealName']}";
* }
*/
function is_logged_in($user = '')
{
$name = substr(cs('txp_login_public'), 10);
if (!strlen($name) or strlen($user) and $user !== $name) {
return false;
}
$rs = safe_row("nonce, name, RealName, email, privs", 'txp_users', "name = '".doSlash($name)."'");
if ($rs and substr(md5($rs['nonce']), -10) === substr(cs('txp_login_public'), 0, 10)) {
unset($rs['nonce']);
return $rs;
} else {
return false;
}
}
/**
* Updates the path to the site.
*
* @param string $here The path
* @access private
* @package Pref
*/
function updateSitePath($here)
{
set_pref('path_to_site', $here, 'publish', PREF_HIDDEN);
}
/**
* Converts Textpattern tag's attribute list to an array.
*
* @param string $text The attribute list, e.g. foobar="1" barfoo="0"
* @return array Array of attributes
* @access private
* @package TagParser
*/
function splat($text)
{
static $stack = array(), $parse = array(), $global_atts = array(), $globals = null;
global $production_status, $trace, $txp_atts;
if ($globals === null) {
$globals = array_filter(Txp::get('\Textpattern\Tag\Registry')->getRegistered(true));
}
$sha = sha1($text);
if (!isset($stack[$sha])) {
$stack[$sha] = array();
$parse[$sha] = array();
if (preg_match_all('@([\w\-]+)(?:\s*=\s*(?:"((?:[^"]|"")*)"|\'((?:[^\']|\'\')*)\'|([^\s\'"/>]+)))?@s', $text, $match, PREG_SET_ORDER)) {
foreach ($match as $m) {
$name = strtolower($m[1]);
switch (count($m)) {
case 2:
$val = true;
break;
case 3:
$val = str_replace('""', '"', $m[2]);
break;
case 4:
$val = str_replace("''", "'", $m[3]);
if (strpos($m[3], ':') !== false) {
$parse[$sha][] = $name;
}
break;
case 5:
$val = $m[4];
trigger_error(gTxt('attribute_values_must_be_quoted'), E_USER_WARNING);
break;
}
$stack[$sha][$name] = $val;
}
}
$global_atts[$sha] = array_intersect_key($stack[$sha], $globals) or $global_atts[$sha] = null;
}
$txp_atts = $global_atts[$sha];
if (empty($parse[$sha])) {
return $stack[$sha];
}
$atts = $stack[$sha];
if ($production_status !== 'live') {
foreach ($parse[$sha] as $p) {
$trace->start("[attribute '".$p."']");
$atts[$p] = parse($atts[$p]);
$trace->stop('[/attribute]');
if (isset($global_atts[$sha][$p])) {
$txp_atts[$p] = $atts[$p];
}
}
} else {
foreach ($parse[$sha] as $p) {
$atts[$p] = parse($atts[$p]);
if (isset($global_atts[$sha][$p])) {
$txp_atts[$p] = $atts[$p];
}
}
}
return $atts;
}
/**
* Replaces CR and LF with spaces, and drops NULL bytes.
*
* Used for sanitising email headers.
*
* @param string $str The string
* @return string
* @package Mail
* @deprecated in 4.6.0
* @see \Textpattern\Mail\Encode::escapeHeader()
*/
function strip_rn($str)
{
return Txp::get('\Textpattern\Mail\Encode')->escapeHeader($str);
}
/**
* Validates a string as an email address.
*
* <code>
* if (is_valid_email('john.doe@example.com'))
* {
* echo "'john.doe@example.com' validates.";
* }
* </code>
*
* @param string $address The email address
* @return bool
* @package Mail
* @deprecated in 4.6.0
* @see filter_var()
*/
function is_valid_email($address)
{
return (bool) filter_var($address, FILTER_VALIDATE_EMAIL);
}
/**
* Sends an email message as the currently logged in user.
*
* <code>
* if (txpMail('john.doe@example.com', 'Subject', 'Some message'))
* {
* echo "Email sent to 'john.doe@example.com'.";
* }
* </code>
*
* @param string $to_address The receiver
* @param string $subject The subject
* @param string $body The message
* @param string $reply_to The reply to address
* @return bool Returns FALSE when sending failed
* @see \Textpattern\Mail\Compose
* @package Mail
*/
function txpMail($to_address, $subject, $body, $reply_to = null)
{
global $txp_user;
// Send the email as the currently logged in user.
if ($txp_user) {
$sender = safe_row(
"RealName, email",
'txp_users',
"name = '".doSlash($txp_user)."'"
);
if ($sender && is_valid_email(get_pref('publisher_email'))) {
$sender['email'] = get_pref('publisher_email');
}
}
// If not logged in, the receiver is the sender.
else {
$sender = safe_row(
"RealName, email",
'txp_users',
"email = '".doSlash($to_address)."'"
);
}
if ($sender) {
extract($sender);
try {
$message = Txp::get('\Textpattern\Mail\Compose')
->from($email, $RealName)
->to($to_address)
->subject($subject)
->body($body);
if ($reply_to) {
$message->replyTo($reply_to);
}
$message->send();
} catch (\Textpattern\Mail\Exception $e) {
return false;
}
return true;
}
return false;
}
/**
* Encodes a string for use in an email header.
*
* @param string $string The string
* @param string $type The type of header, either "text" or "phrase"
* @return string
* @package Mail
* @deprecated in 4.6.0
* @see \Textpattern\Mail\Encode::header()
*/
function encode_mailheader($string, $type)
{
try {
return Txp::get('\Textpattern\Mail\Encode')->header($string, $type);
} catch (\Textpattern\Mail\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
}
}
/**
* Converts an email address into unicode entities.
*
* @param string $txt The email address
* @return string Encoded email address
* @package Mail
* @deprecated in 4.6.0
* @see \Textpattern\Mail\Encode::entityObfuscateAddress()
*/
function eE($txt)
{
return Txp::get('\Textpattern\Mail\Encode')->entityObfuscateAddress($txt);
}
/**
* Strips PHP tags from a string.
*
* @param string $in The input
* @return string
*/
function stripPHP($in)
{
return preg_replace("/".chr(60)."\?(?:php)?|\?".chr(62)."/i", '', $in);
}
/**
* Gets a HTML select field containing all categories, or sub-categories.
*
* @param string $name Return specified parent category's sub-categories
* @param string $cat The selected category option
* @param string $id The HTML ID
* @return string|bool HTML select field or FALSE on error
* @package Form
*/
function event_category_popup($name, $cat = '', $id = '', $atts = array())
{
$rs = getTree('root', $name);
if ($rs) {
return treeSelectInput('category', $rs, $cat, $id, 0, $atts);
}
return false;
}
/**
* Creates a form template.
*
* On a successful run, will trigger a 'form.create > done' callback event.
*
* @param string $name The name
* @param string $type The type
* @param string $Form The template
* @return bool FALSE on error
* @since 4.6.0
* @package Template
*/
function create_form($name, $type, $Form)
{
$types = get_form_types();
if (form_exists($name) || !is_valid_form($name) || !in_array($type, array_keys($types))) {
return false;
}
if (
safe_insert(
'txp_form',
"name = '".doSlash($name)."',
type = '".doSlash($type)."',
Form = '".doSlash($Form)."'"
) === false
) {
return false;
}
callback_event('form.create', 'done', 0, compact('name', 'type', 'Form'));
return true;
}
/**
* Checks if a form template exists.
*
* @param string $name The form
* @return bool TRUE if the form exists
* @since 4.6.0
* @package Template
*/
function form_exists($name)
{
return (bool) safe_row("name", 'txp_form', "name = '".doSlash($name)."'");
}
/**
* Validates a string as a form template name.
*
* @param string $name The form name
* @return bool TRUE if the string validates
* @since 4.6.0
* @package Template
*/
function is_valid_form($name)
{
if (function_exists('mb_strlen')) {
$length = mb_strlen($name, '8bit');
} else {
$length = strlen($name);
}
return $name && !preg_match('/^\s|[<>&"\']|\s$/u', $name) && $length <= 64;
}
/**
* Gets a list of form types.
*
* The list form types can be extended with a 'form.types > types'
* callback event. Callback functions get passed three arguments: '$event',
* '$step' and '$types'. The third parameter contains a reference to an
* array of 'type => label' pairs.
*
* @return array An array of form types
* @since 4.6.0
* @package Template
*/
function get_form_types()
{
static $types = null;
if ($types === null) {
$types = array(
'article' => gTxt('article'),
'misc' => gTxt('misc'),
'comment' => gTxt('comment'),
'category' => gTxt('category'),
'file' => gTxt('file'),
'link' => gTxt('link'),
'section' => gTxt('section'),
);
callback_event_ref('form.types', 'types', 0, $types);
}
return $types;
}
/**
* Gets a list of essential form templates.
*
* These forms can not be deleted or renamed. The array keys hold
* the form names, the array values their group.
*
* The list forms can be extended with a 'form.essential > forms'
* callback event. Callback functions get passed three arguments: '$event',
* '$step' and '$essential'. The third parameter contains a reference to an
* array of forms.
*
* @return array An array of form names
* @since 4.6.0
* @package Template
*/
function get_essential_forms()
{
static $essential = null;
if ($essential === null) {
$essential = array(
'comments' => 'comment',
'comments_display' => 'comment',
'comment_form' => 'comment',
'default' => 'article',
'plainlinks' => 'link',
'files' => 'file',
);
callback_event_ref('form.essential', 'forms', 0, $essential);
}
return $essential;
}
/**
* Updates a list's per page number.
*
* Gets the per page number from a "qty" HTTP POST/GET parameter and
* creates a user-specific preference value "$name_list_pageby".
*
* @param string|null $name The name of the list
* @deprecated in 4.7.0
*/
function event_change_pageby($name = null)
{
global $event;
Txp::get('\Textpattern\Admin\Paginator', $event, $name)->change();
}
/**
* Generic multi-edit form's edit handler shared across panels.
*
* Receives an action from a multi-edit form and runs it in the given
* database table.
*
* @param string $table The database table
* @param string $id_key The database column selected items match to. Column should be integer type
* @return string Comma-separated list of affected items
* @see multi_edit()
*/
function event_multi_edit($table, $id_key)
{
$method = ps('edit_method');
$selected = ps('selected');
if ($selected) {
if ($method == 'delete') {
foreach ($selected as $id) {
$id = assert_int($id);
if (safe_delete($table, "$id_key = '$id'")) {
$ids[] = $id;
}
}
return join(', ', $ids);
}
}
return '';
}
/**
* Gets a "since days ago" date format from a given UNIX timestamp.
*
* @param int $stamp UNIX timestamp
* @return string "n days ago"
* @package DateTime
*/
function since($stamp)
{
$diff = (time() - $stamp);
if ($diff <= 3600) {
$mins = round($diff / 60);
$since = ($mins <= 1) ? ($mins == 1) ? '1 '.gTxt('minute') : gTxt('a_few_seconds') : "$mins ".gTxt('minutes');
} elseif (($diff <= 86400) && ($diff > 3600)) {
$hours = round($diff / 3600);
$since = ($hours <= 1) ? '1 '.gTxt('hour') : "$hours ".gTxt('hours');
} elseif ($diff >= 86400) {
$days = round($diff / 86400);
$since = ($days</