Skip to content
Permalink
dev
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
<?php
/*
* Textpattern Content Management System
* https://textpattern.com/
*
* Copyright (C) 2022 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((string)$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((string)$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://www.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)$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 = isset($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')
{
global $event, $plugin, $txp_current_plugin;
static $txpLang = null;
if ($txpLang === null) {
$txpLang = Txp::get('\Textpattern\L10n\Lang');
$lang = txpinterface == 'admin' ? get_pref('language_ui', gps('lang', LANG)) : LANG;
$loaded = $txpLang->load($lang, true);
$evt = isset($event) ? trim($event) : '';
if (empty($loaded) || !in_array($evt, $loaded)) {
load_lang($lang, $evt);
}
}
// Hackish
if (isset($txp_current_plugin) && isset($plugin['textpack'])) {
$txpLang->loadTextpack($plugin['textpack']);
unset($plugin['textpack']);
}
return $txpLang->txt($var, $atts, $escape);
}
/**
* 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
* @example
* print_r(
* load_lang('en-gb', false)
* );
*/
function load_lang($lang, $events = null)
{
global $production_status, $event, $textarray;
isset($textarray) or $textarray = array();
$textarray = array_merge($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;
}
/**
* 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');
}
/**
* Checks if a user has privileges to the given resource.
*
* @param string $res The resource
* @param mixed $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 = null, $user = '')
{
global $txp_user, $txp_permissions;
static $privs;
if (is_array($user)) {
$level = isset($user['privs']) ? $user['privs'] : null;
$user = isset($user['name']) ? $user['name'] : '';
}
$user = (string) $user;
if ($user === '') {
$user = (string) $txp_user;
}
if ($user !== '') {
if (!isset($privs[$user])) {
$privs[$user] = isset($level) ?
$level :
safe_field("privs", 'txp_users', "name = '".doSlash($user)."'");
}
if (!isset($res)) {
return $privs[$user];
} elseif (isset($txp_permissions[$res]) && $privs[$user] && $txp_permissions[$res]) {
return in_list($privs[$user], $txp_permissions[$res]);
}
}
return false;
}
/**
* Adds dynamic privileges.
*
* @param array $pluggable The array, see global $txp_options
* @since 4.7.2
* @package User
*/
function plug_privs($pluggable = null, $user = null)
{
global $txp_options;
isset($pluggable) or $pluggable = $txp_options;
$level = isset($user['privs']) ? $user['privs'] : has_privs();
foreach ((array)$pluggable as $pref => $pane) {
if (is_array($pane)) {
if (isset($pane[0])) {
if (!in_list($level, $pane[0])) {
break;
}
unset($pane[0]);
}
} else {
$pane = array('prefs.'.$pref => $pane);
}
if (get_pref($pref)) {
array_walk($pane, function (&$item) use ($level) {
if ($item === true) {
$item = $level;
}
});
add_privs($pane);
} else {
add_privs(array_fill_keys(array_keys($pane), null));
}
}
}
/**
* Grants privileges to user-groups.
*
* Will not let you override existing privs.
*
* @param mixed $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 (!is_array($res)) {
$res = array($res => $perm);
}
foreach ($res as $priv => $group) {
if ($group === null) {
$txp_permissions[$priv] = null;
} else {
$group .= (empty($txp_permissions[$priv]) ? '' : ','.$txp_permissions[$priv]);
$group = join(',', do_list_unique($group));
$txp_permissions[$priv] = $group;
}
}
}
/**
* 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, $real = false)
{
global $txp_permissions;
$out = array();
if (isset($txp_permissions[$res])) {
foreach (safe_rows("name, RealName", 'txp_users', "FIND_IN_SET(privs, '".$txp_permissions[$res]."') ORDER BY ".($real ? "RealName" : "name")." ASC") as $user) {
extract($user);
$out[$name] = $real ? $RealName : $name;
}
}
return $out;
}
/**
* 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)
{
$extensions = array(IMAGETYPE_GIF => '.gif', 0 => '.jpeg', IMAGETYPE_JPEG => '.jpg', IMAGETYPE_PNG => '.png') +
(defined('IMAGETYPE_WEBP') ? array(IMAGETYPE_WEBP => '.webp') : array()) +
(defined('IMAGETYPE_AVIF') ? array(IMAGETYPE_AVIF => '.avif') : array());
if (has_privs('image.create.trusted')) {
$extensions += array(IMAGETYPE_SWF => '.swf', IMAGETYPE_SWC => '.swf');
}
callback_event_ref('txp.image', 'types', 0, $extensions);
if (isset($type)) {
return !empty($extensions[$type]) ? $extensions[$type] : false;
}
return $extensions;
}
/**
* 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;
}
/**
* 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)
{
static $mimetypes;
if (($unix_ts = @strtotime($image['date'])) > 0) {
$image['date'] = $unix_ts;
}
if (!isset($mimetypes)) {
$mimetypes = get_safe_image_types();
}
$image['mime'] = ($mime = array_search($image['ext'], $mimetypes)) !== false ? image_type_to_mime_type($mime) : '';
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 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 = '')
{
global $pretext;
if (isset($_GET[$thing])) {
$out = $_GET[$thing];
$out = doArray($out, 'deCRLF');
} elseif (isset($_POST[$thing])) {
$out = $_POST[$thing];
} elseif (is_numeric($thing) && isset($pretext[abs($thing)])) {
$thing >= 0 or $thing += $pretext[0] + 1;
$out = $pretext[$thing];
} else {
return $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 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])) {
$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)) {
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])) {
return $_COOKIE["txp_".$thing];
} elseif (isset($_POST[$thing])) {
return $_POST[$thing];
}
return '';
}
/**
* Gets a HTTP cookie.
*
* @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])) {
return $_COOKIE[$thing];
}
return '';
}
/**
* Sets a HTTP cookie (polyfill).
*
* @param string $name The cookie name
* @param string $value The cookie value
* @param array $options The cookie options
* @package Network
*/
function set_cookie($name, $value = '', $options = array())
{
$options += array (
'expires' => time() - 3600,
'path' => '',
'domain' => '',
'secure' => strtolower(PROTOCOL) == 'https://',
'httponly' => false,
'samesite' => 'Lax' // None || Lax || Strict
);
if (version_compare(phpversion(), '7.3.0') >= 0) {
return setcookie($name, $value, $options);
}
extract($options);
return setcookie($name, $value, $expires, $path.'; samesite='.$samesite, $domain, $secure, $httponly);
}
/**
* 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 $plugin, $plugins, $plugins_ver, $prefs, $txp_current_plugin, $textarray;
if (is_array($plugins) && 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;
$old_plugin = isset($plugin) ? $plugin : null;
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] = isset($plugin['version']) ? $plugin['version'] : 0;
if (isset($plugin['textpack'])) {
Txp::get('\Textpattern\L10n\Lang')->loadTextpack($plugin['textpack']);
}
restore_error_handler();
$plugin = $old_plugin;
return true;
}
}
$version = safe_field("version", 'txp_plugin', ($force ? '' : "status = 1 AND ")."name = '".doSlash($name)."'");
if ($version !== false) {
$plugins[] = $name;
$plugins_ver[$name] = $version;
set_error_handler("pluginErrorHandler");
if (isset($txp_current_plugin)) {
$txp_parent_plugin = $txp_current_plugin;
}
$txp_current_plugin = $name;
$dir = sanitizeForFile($name);
$filename = PLUGINPATH.DS.$dir.DS.$dir.'.php';
if (!is_file($filename)) {
$code = safe_field("code", 'txp_plugin', "name = '".doSlash($name)."'");
\Txp::get('\Textpattern\Plugin\Plugin')->updateFile($txp_current_plugin, $code);
}
$ok = is_readable($filename) ? include_once($filename) : false;
$txp_current_plugin = isset($txp_parent_plugin) ? $txp_parent_plugin : null;
restore_error_handler();
return $ok;
}
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, $plugins_ver;
$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;
}
$version = empty($plugins_ver[$txp_current_plugin]) ? '' : ' ('.$plugins_ver[$txp_current_plugin].')';
printf(
'<pre dir="auto">'.gTxt('plugin_load_error').' <b>%s%s</b> -> <b>%s: %s on line %s</b></pre>',
$txp_current_plugin,
$version,
$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 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">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, $pre = null)
{
global $prefs, $plugins, $plugins_ver, $app_mode, $trace;
static $rs = null;
$trace->start('[Loading plugins]');
is_array($plugins) or $plugins = array();
if (!isset($rs)) {
if (!empty($prefs['plugin_cache_dir'])) {
$dir = rtrim($prefs['plugin_cache_dir'], DS).DS;
// In case it's a relative path.
if (!is_dir($dir)) {
$dir = rtrim(realpath(txpath.DS.$dir), DS).DS;
}
$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').')'.
($plugins ? ' AND name NOT IN ('.join(',', quote_list($plugins)).')' : '');
$rs = safe_rows("name, version, load_order", 'txp_plugin', $where." ORDER BY load_order ASC, name ASC");
}
if ($rs) {
$old_error_handler = set_error_handler("pluginErrorHandler");
$pre = intval($pre);
$writable = is_dir(PLUGINPATH) && is_writable(PLUGINPATH);
foreach ($rs as $a) {
if (!isset($plugins_ver[$a['name']]) && (!$pre || $a['load_order'] < $pre)) {
$plugins[] = $a['name'];
$plugins_ver[$a['name']] = $a['version'];
$GLOBALS['txp_current_plugin'] = $a['name'];
$trace->start("[Loading plugin: '{$a['name']}' version '{$a['version']}']");
$dir = $a['name'];
$filename = PLUGINPATH.DS.$dir.DS.$dir.'.php';
if ($writable && !is_file($filename)) {
$code = safe_field('code', 'txp_plugin', "name='".doSlash($a['name'])."'");
\Txp::get('\Textpattern\Plugin\Plugin')->updateFile($a['name'], $code);
}
$eval_ok = is_readable($filename) ? include($filename) : false;
$trace->stop();
if ($eval_ok === false) {
trigger_error(gTxt('plugin_include_error', array('{name}' => $a['name'])), E_USER_WARNING);
}
unset($GLOBALS['txp_current_plugin']);
}
}
restore_error_handler();
}
$trace->stop();
}
/**
* Attaches 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;
$pre or $pre = 0;
isset($plugin_callback[$event]) or $plugin_callback[$event] = array();
isset($plugin_callback[$event][$pre]) or $plugin_callback[$event][$pre] = array();
isset($plugin_callback[$event][$pre][$step]) or $plugin_callback[$event][$pre][$step] =
isset($plugin_callback[$event][$pre]['']) ? $plugin_callback[$event][$pre][''] : array();
if ($step === '') {
foreach($plugin_callback[$event][$pre] as $key => $val) {
$plugin_callback[$event][$pre][$key][] = $func;
}
} else {
$plugin_callback[$event][$pre][$step][] = $func;
}
}
/**
* 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 $production_status, $trace;
list($pre, $renew) = (array)$pre + array(0, null);
$callbacks = callback_handlers($event, $step, $pre, false);
if (empty($callbacks)) {
return '';
}
$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 ($callbacks as $c) {
if (is_callable($c)) {
if ($production_status !== 'live') {
$trace->start("\t[Call function: '".Txp::get('\Textpattern\Type\TypeCallable', $c)->toString()."'".
(empty($argv) ? '' : ", argv='".serialize($argv)."'")."]");
}
$return_value = call_user_func_array($c, array_merge(array(
$event,
$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}' => Txp::get('\Textpattern\Type\TypeCallable', $c)->toString())), 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 $production_status;
$callbacks = callback_handlers($event, $step, $pre, false);
if (empty($callbacks)) {
return array();
}
$return_value = array();
foreach ($callbacks as $c) {
if (is_callable($c)) {
// 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($event, $step, $data, $options);
} elseif ($production_status == 'debug') {
trigger_error(gTxt('unknown_callback_function', array('{function}' => Txp::get('\Textpattern\Type\TypeCallable', $c)->toString())), E_USER_WARNING);
}
}
return $return_value;
}
/**
* 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;
$pre or $pre = 0;
$step or $step = 0;
$callbacks = isset($plugin_callback[$event][$pre][$step]) ? $plugin_callback[$event][$pre][$step] :
(isset($plugin_callback[$event][$pre]['']) ? $plugin_callback[$event][$pre][''] : array());
if (!$as_string) {
return $callbacks;
}
$out = array();
foreach ($callbacks as $c) {
$out[] = Txp::get('\Textpattern\Type\TypeCallable', $c)->toString();
}
return $out;
}
/**
* 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, $partial;
if ($globals === null) {
$global_atts = Txp::get('\Textpattern\Tag\Registry')->getRegistered(true);
$globals = array_filter($global_atts);
}
if (isset($atts['yield']) && !isset($pairs['yield'])) {
isset($partial) or $partial = Txp::get('\Textpattern\Tag\Registry')->getTag('yield');
foreach (parse_qs($atts['yield']) as $name => $alias) {
$value = call_user_func($partial, array('name' => $alias === false ? $name : $alias));
if (isset($value)) {
$atts[$name] = $value;
}
}
unset($atts['yield']);
}
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, '/[^\p{L}\p{N}\-_\s\/\\\\\x{1F300}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{2600}-\x{27BF}]/u'), '-');
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
* @param string $strip The regex of the characters to strip
* @return string
* @package URL
*/
function sanitizeForUrl($text, $strip = '/[^\p{L}\p{N}\-_\s\/\\\\]/u')
{
$out = callback_event('sanitize_for_url', '', 0, $text);
if ($out !== '') {
return $out;
}
// Remove named entities and tags.
$text = preg_replace("/(^|&\S+;)|(<[^>]*>)/U", "", dumbDown($text));
// Remove all characters except letter, number, dash, space and backslash
$text = preg_replace($strip, '', $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));
}
/**
* Sanitizes 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 blocklist.
*
* @param string $ip The IP address
* @param string|array $checks The checked lists. Defaults to 'spam_blocklists' preferences string
* @return string|bool The lists the IP is on or FALSE
* @package Comment
* @example
* if (is_blocklisted('192.0.2.1'))
* {
* echo "'192.0.2.1' is on the blocklist.";
* }
*/
function is_blocklisted($ip, $checks = '')
{
if (!$checks) {
$checks = do_list_unique(get_pref('spam_blocklists'));
}
$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 = '')
{
static $users = array();
$name = substr(cs('txp_login_public'), 10);
if (!strlen($name) || strlen($user) && $user !== $name) {
return false;
}
if (!isset($users[$name])) {
$users[$name] = safe_row("nonce, name, RealName, email, privs", 'txp_users', "name = '".doSlash($name)."'");
}
$rs = $users[$name];
if ($rs && 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 array|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));
}
if (is_array($text)) {
$txp_atts = array_intersect_key($text, $globals);
return $text;
}
$sha = txp_hash($text);
if (!isset($stack[$sha])) {
$stack[$sha] = $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]);