View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
3823 | Composr website (compo.sr) | General / Uncategorised | public | 2019-06-15 15:21 | 2019-06-26 17:10 |
Reporter | RailDude64 | Assigned To | Chris Graham | ||
Priority | normal | Severity | major | ||
Status | resolved | Resolution | fixed | ||
Summary | 3823: Uncaught TypeError: Cannot read property 'cloneNode' of null → tree_list.js | ||||
Description | I have noticed this error on two different sites (sarhm.org and blmiers.com) when trying to do two different tasks (there may be others that I have not found) 1) moving a topic to a different forum and 2) Mass-add to gallery. | ||||
Steps To Reproduce | Move topic. From Forums -Click to check marker next to the topic to move -Select Move topics in the Topic/poll actions dropdown Error is displayed...after clicking OK the Destination form displays only "The current selection is ‘[forum RecID]’." Mass-add to gallery From Admin Zone -Click Content -Click Galleries Click on the Mass-add to gallery Error is displayed. will not display the listing of existing galleries thus unable to select gallery | ||||
Additional Information | Composr: 10.0.26 Linux: 2.6.32-954.3.5.lve1.4.64.el6.x86_64 PHP: 5.6.39 MySQL: 10.2.12-MariaDB-log | ||||
Tags | No tags attached. | ||||
Attach Tags | |||||
Attached Files | global3.php (138,404 bytes)
<?php /* Composr Copyright (c) ocProducts, 2004-2016 See text/EN/licence.txt for full licencing information. NOTE TO PROGRAMMERS: Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes **** */ /** * @license http://opensource.org/licenses/cpal_1.0 Common Public Attribution License * @copyright ocProducts Ltd * @package core */ /* global3.php contains further support functions, which are shared between the installer and the main installation (i.e. global.php and global2.php are not used by the installer, and the installer emulates these functions functionality via minikernel.php). */ /** * Standard code module initialisation function. * * @ignore */ function init__global3() { global $PAGE_NAME_CACHE, $GETTING_PAGE_NAME; $PAGE_NAME_CACHE = null; $GETTING_PAGE_NAME = false; global $IS_MOBILE_CACHE, $IS_MOBILE_TRUTH_CACHE; $IS_MOBILE_CACHE = null; $IS_MOBILE_TRUTH_CACHE = null; // Heavily optimised string escaping data global $PHP_REP_FROM, $PHP_REP_TO, $PHP_REP_TO_TWICE; $PHP_REP_FROM = array('\\', "\n", '$', '"', "\0"); $PHP_REP_TO = array('\\\\', '\n', '\$', '\\"', '\0'); $PHP_REP_TO_TWICE = array('\\\\\\\\', '\\\\n', '\\\\\\$', '\\\\\"', '\\\\0'); global $BOT_MAP_CACHE, $BOT_TYPE_CACHE; $BOT_MAP_CACHE = null; $BOT_TYPE_CACHE = false; global $LOCALE_FILTER_CACHE; $LOCALE_FILTER_CACHE = null; global $HAS_COOKIES_CACHE; $HAS_COOKIES_CACHE = null; global $BROWSER_MATCHES_CACHE; $BROWSER_MATCHES_CACHE = array(); global $MSN_DB; $MSN_DB = null; // This is like null, but is a higher-precedence null that can also survive string layers (such as HTML forms). It should only be used when: // - 'null' or '' or '-1' aren't appropriate (although '-1' is only appropriate when dealing with numbers held in strings, really). // - OR, as the standard "ignore this field" indicator for query_update (so that "fractional edits" can happen without requiring a secondary API set or a messed up primary API) if (!defined('STRING_MAGIC_NULL')) { define('STRING_MAGIC_NULL', '!--:)abcUNLIKELY'); } // This is similar, but for integers. As before, it should only be used when null and -1 aren't appropriate OR as the "ignore this field" indicator. if (!defined('INTEGER_MAGIC_NULL')) { define('INTEGER_MAGIC_NULL', 1634817353); // VERY unlikely to occur, but is both a 32bit unsigned and a 32 bit signed number } global $ZONE_DEFAULT_PAGES_CACHE; $ZONE_DEFAULT_PAGES_CACHE = array(); global $IS_WIDE_CACHE, $IS_WIDE_HIGH_CACHE; $IS_WIDE_CACHE = null; $IS_WIDE_HIGH_CACHE = null; global $ADDON_INSTALLED_CACHE; $ADDON_INSTALLED_CACHE = array(); global $OUTPUT_STATE_STACK; $OUTPUT_STATE_STACK = array(); // Registry of output state globals global $OUTPUT_STATE_VARS; $OUTPUT_STATE_VARS = array( 'HTTP_STATUS_CODE', 'METADATA', 'ATTACHED_MESSAGES', 'ATTACHED_MESSAGES_RAW', 'LATE_ATTACHED_MESSAGES', 'SEO_KEYWORDS', 'SEO_DESCRIPTION', 'BREADCRUMBS', 'BREADCRUMB_SET_PARENTS', 'DISPLAYED_TITLE', 'SHORT_TITLE', 'FORCE_SET_TITLE', 'BREADCRUMB_SET_SELF', 'FEED_URL', 'FEED_URL_2', 'OUTPUT_STATE_STACK', 'REFRESH_URL', 'FORCE_META_REFRESH', 'QUICK_REDIRECT', 'EXTRA_HEAD', 'EXTRA_FOOT', 'HELPER_PANEL_TEXT', 'HELPER_PANEL_TUTORIAL', 'JAVASCRIPT', 'JAVASCRIPTS', 'JS_OUTPUT_STARTED', 'JAVASCRIPT_BOTTOM', 'CSSS', 'CSS_OUTPUT_STARTED', 'CYCLES', 'TEMPCODE_SETGET', 'COMCODE_PARSE_TITLE', ); _load_blank_output_state(); global $MASS_IMPORT_HAPPENING; $MASS_IMPORT_HAPPENING = false; if (!defined('A_NA')) { // Notifications (defined here, as notification_poller may need them - yet we don't want to include all the notification dispatch code) define('A_NA', 0x0); // Not applicable (0 in decimal) // define('A_INSTANT_EMAIL', 0x2); // (2 in decimal) define('A_DAILY_EMAIL_DIGEST', 0x4); // (4 in decimal) define('A_WEEKLY_EMAIL_DIGEST', 0x8); // (8 in decimal) define('A_MONTHLY_EMAIL_DIGEST', 0x10); // (16 in decimal) define('A_INSTANT_SMS', 0x20); // (32 in decimal) define('A_INSTANT_PT', 0x40); // (64 in decimal) Private topic define('A_WEB_NOTIFICATION', 0x80); // (128 in decimal) Desktop notification if site is open, and always shows on notification dropdown // And... define('A__ALL', 0xFFFFFF); // And... define('A__STATISTICAL', -0x1); // This is magic, it will choose whatever the user probably wants, based on their existing settings define('A__CHOICE', -0x2); // Never stored in DB, used as a flag inside admin_notifications module } global $ESCAPE_HTML_OUTPUT, $KNOWN_TRUE_HTML; // Used to track what is already escaped in kid-gloves modes $ESCAPE_HTML_OUTPUT = array(); $KNOWN_TRUE_HTML = array(); if (!defined('WYSIWYG_COMCODE__BUTTON')) { // Would normally put these in sources/comcode.php, but some of our templating references these constants define('WYSIWYG_COMCODE__BUTTON', 1); define('WYSIWYG_COMCODE__XML_BLOCK', 2); define('WYSIWYG_COMCODE__XML_BLOCK_ESCAPED', WYSIWYG_COMCODE__XML_BLOCK + 4); define('WYSIWYG_COMCODE__XML_BLOCK_ANTIESCAPED', WYSIWYG_COMCODE__XML_BLOCK + 8); define('WYSIWYG_COMCODE__XML_INLINE', 16); define('WYSIWYG_COMCODE__STANDOUT_BLOCK', WYSIWYG_COMCODE__XML_BLOCK + 32); define('WYSIWYG_COMCODE__STANDOUT_INLINE', WYSIWYG_COMCODE__XML_INLINE + 64); define('WYSIWYG_COMCODE__HTML', 128); } global $DOING_OUTPUT_PINGS; $DOING_OUTPUT_PINGS = false; global $DISABLE_SMART_DECACHING_TEMPORARILY; $DISABLE_SMART_DECACHING_TEMPORARILY = false; } /** * Get the file extension of the specified file. It returns without a dot. * * @param string $name The filename * @return string The filename extension (no dot) */ function get_file_extension($name) { $dot_pos = strrpos($name, '.'); if ($dot_pos === false) { return ''; } return strtolower(substr($name, $dot_pos + 1)); } /** * Find whether we can get away with natural file access, not messing with AFMs, world-writability, etc. * * @return boolean Whether we have this */ function is_suexec_like() { if (running_script('webdav')) { return true; // Has to assume so, as cannot intercede } if (GOOGLE_APPENGINE) { return false; } static $answer = null; if ($answer === null) { $answer = (((php_function_allowed('posix_getuid')) && (!isset($_SERVER['HTTP_X_MOSSO_DT'])) && (is_integer(@posix_getuid())) && (@posix_getuid() == @fileowner(get_file_base() . '/' . (running_script('install') ? 'install.php' : 'index.php')))) || (is_writable_wrap(get_file_base() . '/' . (running_script('install') ? 'install.php' : 'index.php')))); } return $answer; } /** * Ensure that the specified file/folder is writeable for the FTP user (so that it can be deleted by the system), and should be called whenever a file is uploaded/created, or a folder is made. We call this function assuming we are giving world permissions. * * @param PATH $path The full pathname to the file/directory * @param ?integer $perms The permissions to make (not the permissions are reduced if the function finds that the file is owned by the web user [doesn't need world permissions then]) (null: default for file/dir) */ function fix_permissions($path, $perms = null) { if (is_null($perms)) { $perms = is_dir($path) ? 0777 : 0666; } // If the file user is different to the FTP user, we need to make it world writeable if ((!is_suexec_like()) || (cms_srv('REQUEST_METHOD') == '')) { @chmod($path, $perms); } else { // Otherwise we do not if ($perms == 0666) { @chmod($path, 0644); } elseif ($perms == 0777) { @chmod($path, 0755); } else { @chmod($path, $perms); } } global $_CREATED_FILES; // From ocProducts PHP version, for development testing if (isset($_CREATED_FILES)) { foreach ($_CREATED_FILES as $i => $x) { if ($x == $path) { unset($_CREATED_FILES[$i]); } } } } /** * Get the contents of a file, with locking support. * * @param PATH $path File path. * @return ~string File contents (false: error) */ function cms_file_get_contents_safe($path) { $tmp = fopen($path, 'rb'); if ($tmp === false) { return false; } flock($tmp, LOCK_SH); $contents = stream_get_contents($tmp); flock($tmp, LOCK_UN); fclose($tmp); return $contents; } /** * Return the file in the URL by downloading it over HTTP. If a byte limit is given, it will only download that many bytes. It outputs warnings, returning null, on error. * * @param URLPATH $url The URL to download * @param ?integer $byte_limit The number of bytes to download. This is not a guarantee, it is a minimum (null: all bytes) * @range 1 max * @param boolean $trigger_error Whether to throw a Composr error, on error * @param boolean $no_redirect Whether to block redirects (returns null when found) * @param string $ua The user-agent to identify as * @param ?array $post_params An optional array of POST parameters to send; if this is null, a GET request is used (null: none) * @param ?array $cookies An optional array of cookies to send (null: none) * @param ?string $accept 'accept' header value (null: don't pass one) * @param ?string $accept_charset 'accept-charset' header value (null: don't pass one) * @param ?string $accept_language 'accept-language' header value (null: don't pass one) * @param ?resource $write_to_file File handle to write to (null: do not do that) * @param ?string $referer The HTTP referer (null: none) * @param ?array $auth A pair: authentication username and password (null: none) * @param float $timeout The timeout (for connecting/stalling, not for overall download time); usually it is rounded up to the nearest second, depending on the downloader implementation * @param boolean $raw_post Whether to treat the POST parameters as a raw POST (rather than using MIME) * @param ?array $files Files to send. Map between field to file path (null: none) * @param ?array $extra_headers Extra headers to send (null: none) * @param ?string $http_verb HTTP verb (null: auto-decide based on other parameters) * @param string $raw_content_type The content type to use if a raw HTTP post * @return ?string The data downloaded (null: error) */ function http_download_file($url, $byte_limit = null, $trigger_error = true, $no_redirect = false, $ua = 'Composr', $post_params = null, $cookies = null, $accept = null, $accept_charset = null, $accept_language = null, $write_to_file = null, $referer = null, $auth = null, $timeout = 6.0, $raw_post = false, $files = null, $extra_headers = null, $http_verb = null, $raw_content_type = 'application/xml') { require_code('files2'); cms_profile_start_for('http_download_file'); $ret = _http_download_file($url, $byte_limit, $trigger_error, $no_redirect, $ua, $post_params, $cookies, $accept, $accept_charset, $accept_language, $write_to_file, $referer, $auth, $timeout, $raw_post, $files, $extra_headers, $http_verb, $raw_content_type); cms_profile_end_for('http_download_file', $url); return $ret; } /** * Load a fresh output state. * * @sets_output_state * * @param boolean $just_tempcode Whether to only restore the Tempcode execution part of the state. * @param boolean $true_blank Whether to go for a completely blank state (no defaults!), not just a default fresh state. * * @ignore */ function _load_blank_output_state($just_tempcode = false, $true_blank = false) { /* Now lots of stuff all relating to output state (unless commented, these GLOBALs should not be written to directly, we have API calls for it) */ if (!$just_tempcode) { global $HTTP_STATUS_CODE; /** Record of the HTTP status code being set. * * @sets_output_state * * @global string $HTTP_STATUS_CODE */ $HTTP_STATUS_CODE = '200'; global $METADATA; $METADATA = array(); global $ATTACHED_MESSAGES, $ATTACHED_MESSAGES_RAW, $LATE_ATTACHED_MESSAGES; $ATTACHED_MESSAGES = null; /** Raw data of attached messages. * * @sets_output_state * * @global ?array $ATTACHED_MESSAGES_RAW */ $ATTACHED_MESSAGES_RAW = array(); $LATE_ATTACHED_MESSAGES = null; global $SEO_KEYWORDS, $SEO_DESCRIPTION, $SHORT_TITLE; $SEO_KEYWORDS = null; $SEO_DESCRIPTION = null; /** Shortened title to use only within header text and thus <title> tag (if not set, $DISPLAYED_TITLE will be used). * * @sets_output_state * * @global ?string $SHORT_TITLE */ $SHORT_TITLE = null; global $BREADCRUMBS, $BREADCRUMB_SET_PARENTS, $DISPLAYED_TITLE, $FORCE_SET_TITLE, $BREADCRUMB_SET_SELF; $BREADCRUMBS = null; $BREADCRUMB_SET_PARENTS = array(); /** The screen title that was set (i.e. <h1>). * * @sets_output_state * * @global ?string $DISPLAYED_TITLE */ $DISPLAYED_TITLE = null; $FORCE_SET_TITLE = false; $BREADCRUMB_SET_SELF = null; global $FEED_URL, $FEED_URL_2; $FEED_URL = null; $FEED_URL_2 = null; global $REFRESH_URL, $FORCE_META_REFRESH, $QUICK_REDIRECT; $REFRESH_URL[0] = ''; $REFRESH_URL[1] = 0; $FORCE_META_REFRESH = false; $QUICK_REDIRECT = false; global $EXTRA_HEAD, $EXTRA_FOOT; $EXTRA_HEAD = null; $EXTRA_FOOT = null; global $HELPER_PANEL_TEXT, $HELPER_PANEL_TUTORIAL; $HELPER_PANEL_TEXT = ''; $HELPER_PANEL_TUTORIAL = ''; // Register basic CSS and JavaScript requirements global $JAVASCRIPT, $JAVASCRIPTS, $CSSS, $JAVASCRIPTS_DEFAULT; $JAVASCRIPT = null; /** List of required JavaScript files. * * @sets_output_state * * @global ?array $JAVASCRIPTS */ $JAVASCRIPTS = $true_blank ? array() : $JAVASCRIPTS_DEFAULT; /** List of required CSS files. * * @sets_output_state * * @global ?array $CSSS */ $CSSS = $true_blank ? array() : array('no_cache' => true, 'global' => true); } global $CYCLES, $TEMPCODE_SETGET; /** Stores Tempcode CYCLE values during execution. * * @sets_output_state * * @global array $CYCLE */ $CYCLES = array(); /** Stores Tempcode variable values during execution. * * @sets_output_state * * @global array $TEMPCODE_SETGET */ $TEMPCODE_SETGET = array(); } /** * Push the output state on the stack and create a fresh one. * * @sets_output_state * * @param boolean $just_tempcode Whether to only restore the Tempcode execution part of the state. * @param boolean $true_blank Whether to go for a completely blank state (no defaults!), not just a default fresh state. */ function push_output_state($just_tempcode = false, $true_blank = false) { global $OUTPUT_STATE_STACK, $OUTPUT_STATE_VARS; $current_state = array(); foreach ($OUTPUT_STATE_VARS as $var) { $current_state[$var] = isset($GLOBALS[$var]) ? $GLOBALS[$var] : null; } array_push($OUTPUT_STATE_STACK, $current_state); _load_blank_output_state($just_tempcode, $true_blank); } /** * Restore the last output state on the stack, or a fresh one if none was pushed. * * @sets_output_state * * @param boolean $just_tempcode Whether to only restore the Tempcode execution part of the state. * @param boolean $merge_current Whether to merge the current output state in. * @param ?array $keep Settings to keep / merge if possible (null: none). */ function restore_output_state($just_tempcode = false, $merge_current = false, $keep = null) { global $OUTPUT_STATE_STACK; if ($keep === null) { $keep = array(); } $mergeable_arrays = array('METADATA' => true, 'JAVASCRIPTS' => true, 'CSSS' => true, 'TEMPCODE_SETGET' => true, 'CYCLES' => true); $mergeable_tempcode = array('EXTRA_HEAD' => true, 'EXTRA_FOOT' => true, 'JAVASCRIPT' => true); $old_state = array_pop($OUTPUT_STATE_STACK); if ($old_state === null) { _load_blank_output_state($just_tempcode); } else { foreach ($old_state as $var => $val) { if ((!$just_tempcode) || ($var == 'CYCLES') || ($var == 'TEMPCODE_SETGET')) { $merge_array = (($merge_current) && (is_array($val)) && (isset($mergeable_arrays[$var]))); $merge_tempcode = (($merge_current) && (isset($val->codename/*faster than is_object*/)) && (isset($mergeable_tempcode[$var]))); $mergeable = $merge_array || $merge_tempcode; if (($keep === array()) || (!in_array($var, $keep)) || ($mergeable)) { if ($merge_array) { if ($GLOBALS[$var] === null) { $GLOBALS[$var] = array(); } $GLOBALS[$var] = array_merge($val, $GLOBALS[$var]); } elseif ($merge_tempcode) { if ($GLOBALS[$var] === null) { $GLOBALS[$var] = new Tempcode(); } $GLOBALS[$var]->attach($val); } elseif (!$merge_current || !isset($GLOBALS[$var]) || $GLOBALS[$var] === array() || $GLOBALS[$var] === false || $GLOBALS[$var] === '' || $var == 'REFRESH_URL') { $GLOBALS[$var] = $val; } } } } } } /** * Turn the Tempcode lump into a standalone page. * * @param ?Tempcode $middle The Tempcode to put into a nice frame (null: support output streaming mode) * @param ?mixed $message 'Additional' message (null: none) * @param string $type The type of special message * @set inform warn "" * @param boolean $include_header_and_footer Whether to include the header/footer/panels * @param boolean $show_border Whether to include a full screen rendering layout (will be overridable by 'show_border' GET parameter if present or if main page view) * @return Tempcode Standalone page */ function globalise($middle, $message = null, $type = '', $include_header_and_footer = false, $show_border = false) { if (!$include_header_and_footer) { // FUDGE $old = mixed(); if (isset($_GET['wide_high'])) { $old = $_GET['wide_high']; } $_GET['wide_high'] = '1'; } require_code('site'); if ($message !== null) { attach_message($message, $type); } restore_output_state(true); // Here we reset some Tempcode environmental stuff, because template compilation or preprocessing may have dirtied things $show_border = (get_param_integer('show_border', $show_border ? 1 : 0) == 1); if (!$show_border && !running_script('index')) { $global = do_template('STANDALONE_HTML_WRAP', array( '_GUID' => 'fe818a6fb0870f0b211e8e52adb23f26', 'TITLE' => ($GLOBALS['DISPLAYED_TITLE'] === null) ? do_lang_tempcode('NA') : $GLOBALS['DISPLAYED_TITLE'], 'FRAME' => running_script('iframe'), 'TARGET' => '_self', 'CONTENT' => $middle, )); if ($GLOBALS['OUTPUT_STREAMING'] || $middle !== null) { $global->handle_symbol_preprocessing(); } return $global; } global $TEMPCODE_CURRENT_PAGE_OUTPUTTING; if (($middle !== null) && (isset($TEMPCODE_CURRENT_PAGE_OUTPUTTING))) { // Error happened after output and during MIDDLE processing, so bind MIDDLE as an error $middle->handle_symbol_preprocessing(); $global = $TEMPCODE_CURRENT_PAGE_OUTPUTTING; $global->singular_bind('MIDDLE', $middle); // NB: We also considered the idea of using document.write() as a way to reset the output stream, but JavaScript execution will not happen before the parser (even if you force a flush and delay) } else { global $DOING_OUTPUT_PINGS; if (headers_sent() && !$DOING_OUTPUT_PINGS) { $global = do_template('STANDALONE_HTML_WRAP', array( '_GUID' => 'd579b62182a0f815e0ead1daa5904793', 'TITLE' => ($GLOBALS['DISPLAYED_TITLE'] === null) ? do_lang_tempcode('NA') : $GLOBALS['DISPLAYED_TITLE'], 'FRAME' => false, 'TARGET' => '_self', 'CONTENT' => $middle, )); } else { $global = do_template('GLOBAL_HTML_WRAP', array( '_GUID' => '592faa2c0e8bf2dc3492de2c11ca7131', 'MIDDLE' => $middle, )); } if ($GLOBALS['OUTPUT_STREAMING'] || $middle !== null) { $global->handle_symbol_preprocessing(); } } if (get_value('xhtml_strict') === '1') { require_code('global4'); $global = make_xhtml_strict($global); } if ((!$include_header_and_footer) && ($old !== null)) { $_GET['wide_high'] = $old; } return $global; } /** * Attach some XHTML to the screen footer. * * @sets_output_state * * @param mixed $data XHTML to attach (Tempcode or string) */ function attach_to_screen_footer($data) { global $EXTRA_FOOT; if ($EXTRA_FOOT === null) { $EXTRA_FOOT = new Tempcode(); } $EXTRA_FOOT->attach($data); } /** * Add some metadata for the request. * * @sets_output_state * * @param array $metadata Extra metadata * @param ?array $row Content row to automatically grab data from, if we also have $content_type (null: unknown) * @param ?ID_TEXT $content_type Content type (null: unknown) * @param ?ID_TEXT $content_id Content ID (null: unknown) */ function set_extra_request_metadata($metadata, $row = null, $content_type = null, $content_id = null) { global $METADATA; $METADATA += $metadata; if ($content_type !== null) { require_code('content'); $cma_ob = get_content_object($content_type); if ($cma_ob !== null) { $cma_info = $cma_ob->info(); if ($cma_ob === null) { $content_type = null; } } else { $content_type = null; } } if ($row !== null && $content_type !== null) { // Add in generic data... $cma_mappings = array( 'created' => 'add_time_field', 'creator' => isset($cma_info['author_field']) ? 'author_field' : 'submitter_field', 'publisher' => 'submitter_field', 'modified' => 'edit_time_field', 'title' => 'title_field', 'description' => 'description_field', 'views' => 'views_field', 'validated' => 'validated_field', 'type' => 'content_type_universal_label', ); foreach ($cma_mappings as $meta_type => $cma_field) { if (!isset($METADATA[$meta_type])) { if ($cma_field == 'content_type_universal_label' || isset($row[$cma_info[$cma_field]])) { switch ($meta_type) { case 'type': $val_raw = $cma_info[$cma_field]; $val = $val_raw; break; case 'created': case 'modified': $val_raw = strval($row[$cma_info[$cma_field]]); $val = date('Y-m-d', $row[$cma_info[$cma_field]]); break; case 'publisher': case 'creator': if ($cma_field == 'author_field') { $val_raw = $row[$cma_info[$cma_field]]; $val = $val_raw; } else { $val_raw = strval($row[$cma_info[$cma_field]]); $val = $GLOBALS['FORUM_DRIVER']->get_username($row[$cma_info[$cma_field]]); } break; case 'title': if ($cma_info['title_field_dereference']) { $val_raw = get_translated_text($row[$cma_info[$cma_field]], $cma_info['connection']); } else { $val_raw = $row[$cma_info[$cma_field]]; } if ($content_type === 'comcode_page') { // FUDGE if ($content_id === ':start') { $val_raw = get_site_name(); } else { $val_raw = titleify($val_raw); } } if ((!isset($cma_info['title_field_supports_comcode'])) || (!$cma_info['title_field_supports_comcode'])) { $val = comcode_escape($val_raw); } else { $val = $val_raw; } break; case 'description': if (is_integer($row[$cma_info[$cma_field]])) { $val_raw = get_translated_text($row[$cma_info[$cma_field]], $cma_info['connection']); } else { $val_raw = $row[$cma_info[$cma_field]]; } $val = $val_raw; break; case 'views': $val_raw = strval($row[$cma_info[$cma_field]]); $val = $val_raw; break; case 'validated': $val_raw = strval($row[$cma_info[$cma_field]]); $val = $val_raw; break; default: $val_raw = $row[$cma_info[$cma_field]]; $val = $val_raw; break; } if ($val !== null) { $METADATA[$meta_type] = $val; $METADATA[$meta_type . '_RAW'] = $val_raw; } } } } // Add in image... $image_url = ''; if ($cma_info['thumb_field'] !== null) { if ((strpos($cma_info['thumb_field'], 'CALL:') !== false) && ($content_id !== null)) { $image_url = call_user_func(trim(substr($cma_info['thumb_field'], 5)), array('id' => $content_id), false); } else { if ($content_type === 'image') { $image_url = $row['url']; // FUDGE } else { $image_url = $row[$cma_info['thumb_field']]; } } if ($image_url != '') { if ($cma_info['thumb_field_is_theme_image']) { $image_url = find_theme_image($image_url, true); } else { if (url_is_local($image_url)) { $image_url = get_custom_base_url() . '/' . $image_url; } } } } if ((empty($image_url)) && ($cma_info['alternate_icon_theme_image'] != '') && ($content_id !== ':start'/*TODO: Fix in v11*/)) { $METADATA['image'] = find_theme_image($cma_info['alternate_icon_theme_image'], true); } if (!empty($image_url)) { $METADATA['image'] = $image_url; } // Add all $cma_info $METADATA += $cma_info; unset($METADATA['connection']); $METADATA['content_type_label_trans'] = do_lang($cma_info['content_type_label']); } if ($content_type !== null) { $METADATA['content_type'] = $content_type; } if ($content_id !== null) { $METADATA['content_id'] = $content_id; } } /** * Set the HTTP status code for the request. * * @sets_output_state * * @param string $code The HTTP status code (should be numeric) */ function set_http_status_code($code) { global $HTTP_STATUS_CODE; $HTTP_STATUS_CODE = $code; // So we can keep track if ((!headers_sent()) && (function_exists('browser_matches')) && (!browser_matches('ie')) && (strpos(cms_srv('SERVER_SOFTWARE'), 'IIS') === false)) { switch ($code) { case '301': header('HTTP/1.0 301 Moved Permanently'); break; case '400': header('HTTP/1.0 400 Bad Request'); break; case '401': header('HTTP/1.0 401 Unauthorized'); break; case '403': header('HTTP/1.0 403 Forbidden'); break; case '404': header('HTTP/1.0 404 Not Found'); break; case '429': header('HTTP/1.0 429 Too Many Requests'); break; case '500': header('HTTP/1.0 500 Internal server error'); break; } } } /** * Search for a template. * * @param ID_TEXT $codename The codename of the template being loaded * @param ?LANGUAGE_NAME $lang The language to load the template in (templates can embed language references) (null: users own language) * @param ID_TEXT $theme The theme to use * @param string $suffix File type suffix of template file (e.g. .tpl) * @param string $directory Subdirectory type to look in * @set templates css * @param boolean $non_custom_only Whether to only search in the default templates * @param boolean $fallback_other_themes Allow fallback to other themes, in case it is defined only in a specific theme we would not noprmally look in * @return ?array List of parameters needed for the _do_template function to be able to load the template (null: could not find the template) */ function find_template_place($codename, $lang, $theme, $suffix, $directory, $non_custom_only = false, $fallback_other_themes = true) { global $FILE_ARRAY, $CURRENT_SHARE_USER; static $tp_cache = array(); $sz = serialize(array($codename, $lang, $theme, $suffix, $directory, $non_custom_only)); if (isset($tp_cache[$sz])) { return $tp_cache[$sz]; } if (addon_installed('less') && $suffix == '.css') { $test = find_template_place($codename, $lang, $theme, '.less', $directory, $non_custom_only, false); if (!is_null($test)) { $tp_cache[$sz] = $test; return $test; } } $prefix_default = get_file_base() . '/themes/'; $prefix = ($theme == 'default' || $theme == 'admin') ? $prefix_default : (get_custom_file_base() . '/themes/'); if (!isset($FILE_ARRAY)) { if ((is_file($prefix . $theme . '/' . $directory . '_custom/' . $codename . $suffix)) && (!in_safe_mode()) && (!$non_custom_only)) { $place = array($theme, '/' . $directory . '_custom/', $suffix); } elseif (is_file($prefix . $theme . '/' . $directory . '/' . $codename . $suffix)) { $place = array($theme, '/' . $directory . '/', $suffix); } elseif (($CURRENT_SHARE_USER !== null) && ($theme !== 'default') && (is_file(get_file_base() . '/themes/' . $theme . '/' . $directory . '_custom/' . $codename . $suffix)) && (!$non_custom_only)) { $place = array($theme, '/' . $directory . '_custom/', $suffix); } elseif (($CURRENT_SHARE_USER !== null) && ($theme !== 'default') && (is_file(get_file_base() . '/themes/' . $theme . '/' . $directory . '/' . $codename . $suffix))) { $place = array($theme, '/' . $directory . '/', $suffix); } elseif (($CURRENT_SHARE_USER !== null) && (is_file(get_custom_file_base() . '/themes/default/' . $directory . '_custom/' . $codename . $suffix)) && (!$non_custom_only)) { $place = array('default', '/' . $directory . '_custom/', $suffix); } elseif (($CURRENT_SHARE_USER !== null) && (is_file(get_custom_file_base() . '/themes/default/' . $directory . '/' . $codename . $suffix))) { $place = array('default', '/' . $directory . '/', $suffix); } elseif ((is_file($prefix_default . 'default' . '/' . $directory . '_custom/' . $codename . $suffix)) && (!in_safe_mode()) && (!$non_custom_only)) { $place = array('default', '/' . $directory . '_custom/', $suffix); } elseif (is_file($prefix_default . 'default' . '/' . $directory . '/' . $codename . $suffix)) { $place = array('default', '/' . $directory . '/', $suffix); } else { $place = null; } if (($place === null) && (!$non_custom_only) && ($fallback_other_themes)) { // Get desperate, search in themes other than current and default $dh = opendir(get_file_base() . '/themes'); while (($possible_theme = readdir($dh))) { if (($possible_theme[0] !== '.') && ($possible_theme !== 'default') && ($possible_theme !== $theme) && ($possible_theme !== 'map.ini') && ($possible_theme !== 'index.html')) { $full_path = get_custom_file_base() . '/themes/' . $possible_theme . '/' . $directory . '_custom/' . $codename . $suffix; if (is_file($full_path)) { $place = array($possible_theme, '/' . $directory . '_custom/', $suffix); break; } } } closedir($dh); } } else { $place = array('default', '/' . $directory . '/', $suffix); } $tp_cache[$sz] = $place; return $place; } /** * Find whether panels and the header/footer areas won't be shown. * * @return BINARY Result. */ function is_wide_high() { global $IS_WIDE_HIGH_CACHE; if ($IS_WIDE_HIGH_CACHE !== null) { return $IS_WIDE_HIGH_CACHE; } $IS_WIDE_HIGH_CACHE = get_param_integer('wide_high', get_param_integer('keep_wide_high', get_param_integer('wide_print', 0))); return $IS_WIDE_HIGH_CACHE; } /** * Find whether panels will be shown. * * @return BINARY Result. */ function is_wide() { global $IS_WIDE_CACHE; if ($IS_WIDE_CACHE !== null) { return $IS_WIDE_CACHE; } global $ZONE; $IS_WIDE_CACHE = get_param_integer('wide', get_param_integer('keep_wide', (is_wide_high() == 1) ? 1 : 0)); if ($IS_WIDE_CACHE == 0) { return 0; } // Need to check it is allowed $theme = $GLOBALS['FORUM_DRIVER']->get_theme(); $ini_path = (($theme == 'default' || $theme == 'admin') ? get_file_base() : get_custom_file_base()) . '/themes/' . $theme . '/theme.ini'; if (is_file($ini_path)) { require_code('files'); $details = better_parse_ini_file($ini_path); if ((isset($details['supports_wide'])) && ($details['supports_wide'] == '0')) { $IS_WIDE_CACHE = 0; return $IS_WIDE_CACHE; } } return $IS_WIDE_CACHE; } /** * Fixes bad unicode (utf-8) in the input. Useful when input may be dirty, e.g. from a txt file, or from a potential hacker. * The fix is imperfect, it will actually treat the input as ISO-8859-1 if not valid utf-8, then reconvert. Some limited scrambling is considered better than a stack trace. * This function does nothing if we are not using utf-8. * * @param string $input Input string * @param boolean $definitely_unicode If we know the input is meant to be unicode * @return string Guaranteed valid utf-8, if we're using it, otherwise the same as the input string */ function fix_bad_unicode($input, $definitely_unicode = false) { // Fix bad unicode if (get_charset() == 'utf-8' || $definitely_unicode) { if (is_numeric($input) || preg_match('#[^\x00-\x7f]#', $input) == 0) { return $input; // No non-ASCII characters } $test_string = $input; // avoid being destructive $test_string = preg_replace('#[\x09\x0A\x0D\x20-\x7E]#', '', $test_string); // ASCII $test_string = preg_replace('#[\xC2-\xDF][\x80-\xBF]#', '', $test_string); // non-overlong 2-byte $test_string = preg_replace('#\xE0[\xA0-\xBF][\x80-\xBF]#', '', $test_string); // excluding overlongs $test_string = preg_replace('#[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}#', '', $test_string); // straight 3-byte $test_string = preg_replace('#\xED[\x80-\x9F][\x80-\xBF]#', '', $test_string); // excluding surrogates $test_string = preg_replace('#\xF0[\x90-\xBF][\x80-\xBF]{2}#', '', $test_string); // planes 1-3 $test_string = preg_replace('#[\xF1-\xF3][\x80-\xBF]{3}#', '', $test_string); // planes 4-15 $test_string = preg_replace('#\xF4[\x80-\x8F][\x80-\xBF]{2}#', '', $test_string); // plane 16 if ($test_string !== '') { // All ASCII/unicode characters stripped, so if anything is remaining it must be some kind of corruption $input = utf8_encode($input); } } return $input; } /** * Get string length, with utf-8 awareness where possible/required. * * @param string $in The string to get the length of. * @param boolean $force Whether to force unicode as on. * @return integer The string length. */ function cms_mb_strlen($in, $force = false) { if (!$force && get_charset() != 'utf-8') { return strlen($in); } if (function_exists('mb_strlen')) { return @mb_strlen($in, $force ? 'utf-8' : get_charset()); // @ is because there could be invalid unicode involved } if (function_exists('iconv_strlen')) { return @iconv_strlen($in, $force ? 'utf-8' : get_charset()); } return strlen($in); } /** * Return part of a string, with utf-8 awareness where possible/required. * * @param string $in The subject. * @param integer $from The start position. * @param ?integer $amount The length to extract (null: all remaining). * @param boolean $force Whether to force unicode as on. * @return ~string String part (false: $start was over the end of the string). */ function cms_mb_substr($in, $from, $amount = null, $force = false) { if ($amount === null) { $amount = cms_mb_strlen($in, $force) - $from; } if ($in == '' || strlen($in) == $from) { return ''; // Workaround PHP bug/inconsistency (https://bugs.php.net/bug.php?id=72320) } if ((!$force) && (get_charset() != 'utf-8')) { return substr($in, $from, $amount); } if (function_exists('iconv_substr')) { return @iconv_substr($in, $from, $amount, $force ? 'utf-8' : get_charset()); } if (function_exists('mb_substr')) { return @mb_substr($in, $from, $amount, $force ? 'utf-8' : get_charset()); } $ret = substr($in, $from, $amount); $end = ord(substr($ret, -1)); if (($end >= 192) && ($end <= 223)) { $ret .= substr($in, $from + $amount, 1); } if ($from != 0) { $start = ord(substr($ret, 0, 1)); if (($start >= 192) && ($start <= 223)) { $ret = substr($in, $from - 1, 1) . $ret; } } return $ret; } /** * Make a string title-case, with utf-8 awareness where possible/required. * * @param string $in Subject. * @return string Result. */ function cms_mb_ucwords($in) { if (get_charset() != 'utf-8') { return ucwords($in); } if (function_exists('mb_convert_case')) { return @mb_convert_case($in, MB_CASE_TITLE, get_charset()); } return ucwords($in); } /** * Make a string lowercase, with utf-8 awareness where possible/required. * * @param string $in Subject. * @return string Result. */ function cms_mb_strtolower($in) { if (get_charset() != 'utf-8') { return strtolower($in); } if (function_exists('mb_strtolower')) { return @mb_strtolower($in, get_charset()); } return strtolower($in); } /** * Make a string uppercase, with utf-8 awareness where possible/required. * * @param string $in Subject. * @return string Result. */ function cms_mb_strtoupper($in) { if (get_charset() != 'utf-8') { return strtoupper($in); } if (function_exists('mb_strtoupper')) { return @mb_strtoupper($in, get_charset()); } return strtoupper($in); } /** * Find if we a string is ASCII, and hence we can use non-UTF-safe functions on it. * * @param string $x String to test * @return boolean Whether it is ASCII */ function is_ascii_string($x) { $l = strlen($x); for ($i = 0; $i < $l; $i++) { if (ord($x[$i]) >= 128) { return false; } } return true; } /** * Find whether a file/directory is writeable. This function is designed to get past that the PHP is_writable function does not work properly on Windows. * * @param PATH $path The file path * @return boolean Whether the file is writeable */ function is_writable_wrap($path) { if (strtoupper(substr(PHP_OS, 0, 3)) != 'WIN') { return is_writable($path); } if (!file_exists($path)) { return false; } if (is_dir($path)) { /*if (false) { // ideal, but too dangerous as sometimes you can write files but not delete again $test = @fopen($path . '/cms.delete.me', GOOGLE_APPENGINE ? 'wb' : 'wt'); if ($test !== false) { fclose($test); unlink($path . '/cms.delete.me'); return true; } return false; }*/ return is_writable($path); // imperfect unfortunately; but unlikely to cause a problem } else { $test = @fopen($path, 'ab'); if ($test !== false) { fclose($test); return true; } return false; } } /** * Discern the cause of a file-write error, and show an appropriate error message. * * @param PATH $path File path that could not be written (full path, not relative) */ function intelligent_write_error($path) { require_code('files2'); _intelligent_write_error($path); } /** * Discern the cause of a file-write error, and return an appropriate error message. * * @param PATH $path File path that could not be written * @return Tempcode Message */ function intelligent_write_error_inline($path) { require_code('files2'); return _intelligent_write_error_inline($path); } /** * Find whether we have no forum on this website. * * @return boolean Whether we have no forum on this website */ function has_no_forum() { if (get_forum_type() == 'none') { return true; } if ((get_forum_type() == 'cns') && (!addon_installed('cns_forum'))) { return true; } return false; } /** * Check to see if an addon is installed. * * @param ID_TEXT $addon The addon name * @param boolean $non_bundled_too Whether to check non-bundled addons (ones without an addon_registry hook) * @return boolean Whether it is */ function addon_installed($addon, $non_bundled_too = false) { global $ADDON_INSTALLED_CACHE, $SITE_INFO; if ($ADDON_INSTALLED_CACHE == array()) { if (function_exists('persistent_cache_get')) { $ADDON_INSTALLED_CACHE = persistent_cache_get('ADDONS_INSTALLED'); } } if (isset($ADDON_INSTALLED_CACHE[$addon])) { return $ADDON_INSTALLED_CACHE[$addon]; } $addon = filter_naughty($addon, true); $answer = is_file(get_file_base() . '/sources/hooks/systems/addon_registry/' . $addon . '.php') || is_file(get_file_base() . '/sources_custom/hooks/systems/addon_registry/' . $addon . '.php'); if ((!$answer) && ($non_bundled_too) && (!running_script('install'))) { $test = $GLOBALS['SITE_DB']->query_select_value_if_there('addons', 'addon_name', array('addon_name' => $addon)); if ($test !== null) { $answer = true; } } $ADDON_INSTALLED_CACHE[$addon] = $answer; if (function_exists('persistent_cache_set')) { persistent_cache_set('ADDONS_INSTALLED', $ADDON_INSTALLED_CACHE); } return $answer; } /** * Convert a float to a "technical string representation of a float". Inverted with floatval. * * @param float $num The number * @param integer $decs_wanted The number of decimals to keep * @param boolean $only_needed_decs Whether to trim trailing zeros * @return string The string converted */ function float_to_raw_string($num, $decs_wanted = 2, $only_needed_decs = false) { $str = number_format($num, $decs_wanted, '.', ''); $dot_pos = strpos($str, '.'); $decs_here = ($dot_pos === false) ? 0 : (strlen($str) - $dot_pos - 1); if ($decs_here < $decs_wanted) { for ($i = 0; $i < $decs_wanted - $decs_here; $i++) { $str .= '0'; } } elseif ($decs_here > $decs_wanted) { $str = substr($str, 0, strlen($str) - $decs_here + $decs_wanted); if ($decs_wanted == 0 && !$only_needed_decs) { $str = rtrim($str, '.'); } } if ($only_needed_decs && $decs_wanted != 0) { $str = rtrim(rtrim($str, '0'), '.'); } return $str; } /** * Format the given float number as a nicely formatted string (using the locale). Inverted with float_unformat. * * @param float $val The value to format * @param integer $decs_wanted The number of fractional digits * @param boolean $only_needed_decs Whether to trim trailing zeros * @return string Nicely formatted string */ function float_format($val, $decs_wanted = 2, $only_needed_decs = false) { $locale = localeconv(); if ($locale['thousands_sep'] == '') { $locale['thousands_sep'] = ','; } $str = number_format($val, $decs_wanted, $locale['decimal_point'], $locale['thousands_sep']); $dot_pos = strpos($str, '.'); $decs_here = ($dot_pos === false) ? 0 : (strlen($str) - $dot_pos - 1); if ($decs_here < $decs_wanted) { for ($i = 0; $i < $decs_wanted - $decs_here; $i++) { $str .= '0'; } } elseif ($decs_here > $decs_wanted) { $str = substr($str, 0, strlen($str) - $decs_here + $decs_wanted); if ($decs_wanted == 0) { $str = rtrim($str, '.'); } } if ($only_needed_decs && $decs_wanted != 0) { $str = rtrim(rtrim($str, '0'), '.'); } return $str; } /** * Take the given formatted float number and convert it to a native float. The inverse of float_format. * * @param string $str The formatted float number using the locale. * @param boolean $no_thousands_sep Whether we do *not* expect a thousands separator, which means we can be a bit smarter. * @return float Native float */ function float_unformat($str, $no_thousands_sep = false) { $locale = localeconv(); // Simplest case? if (preg_match('#^\d+$#', $str) != 0) { // E.g. "123" return floatval($str); } if ($no_thousands_sep) { // We can assume a "." is a decimal point then? if (preg_match('#^\d+\.\d+$#', $str) != 0) { // E.g. "123.456" return floatval($str); } } // Looks like English-format? It couldn't be anything else because thousands_sep always comes before decimal_point if (preg_match('#^[\d,]+\.\d+$#', $str) != 0) { // E.g. "123,456.789" return floatval($str); } // Now it must e E.g. "123.456,789" or "123.456", or something from another language which uses other separators... if ($locale['thousands_sep'] != '') { $str = str_replace($locale['thousands_sep'], '', $str); } $str = str_replace($locale['decimal_point'], '.', $str); return floatval($str); } /** * Format the given integer number as a nicely formatted string (using the locale). * * @param integer $val The value to format * @return string Nicely formatted string */ function integer_format($val) { static $locale = null; if ($locale === null) { $locale = localeconv(); if ($locale['thousands_sep'] == '') { $locale['thousands_sep'] = ','; } } return number_format($val, 0, $locale['decimal_point'], $locale['thousands_sep']); } /** * Sort a list of maps by the string length of a particular key ID in the maps. * * @param array $rows List of maps to sort * @param mixed $sort_key Either an integer sort key (to sort by integer key ID of contained arrays) or a String sort key (to sort by string key ID of contained arrays). */ function sort_maps_by__strlen($rows, $sort_key) { global $M_SORT_KEY; $M_SORT_KEY = $sort_key; if (count($rows) < 2) { if (($GLOBALS['DEV_MODE']) && (count($rows) == 1)) { call_user_func('_strlen_sort', current($rows), current($rows)); // Just to make sure there's no crash bug in the sort function } return; } @uasort($rows, '_strlen_sort'); // @ is to stop PHP bug warning about altered array contents when Tempcode copies are evaluated internally } /** * Helper function for usort to sort a list by string length. * * @param string $a The first string to compare * @param string $b The second string to compare * @return integer The comparison result (0 for equal, -1 for less, 1 for more) * @ignore */ function _strlen_sort($a, $b) { if (!isset($a)) { $a = ''; } if (!isset($b)) { $b = ''; } if ($a == $b) { return 0; } if (!is_string($a)) { global $M_SORT_KEY; return (strlen($a[$M_SORT_KEY]) < strlen($b[$M_SORT_KEY])) ? -1 : 1; } return (strlen($a) < strlen($b)) ? -1 : 1; } /** * Sort a list of maps by a particular key ID in the maps. Does not (and should not) preserve list indices, but does preserve associative key indices. * * @param array $rows List of maps to sort * @param mixed $sort_keys Either an integer sort key (to sort by integer key ID of contained arrays) or a Comma-separated list of sort keys (to sort by string key ID of contained arrays; prefix '!' a key to reverse the sort order for it). * @param boolean $preserve_order_if_possible Don't shuffle order unnecessarily (i.e. do a merge sort) */ function sort_maps_by(&$rows, $sort_keys, $preserve_order_if_possible = false) { if ($rows == array()) { return; } global $M_SORT_KEY; $M_SORT_KEY = $sort_keys; if ($preserve_order_if_possible) { merge_sort($rows, '_multi_sort'); } else { if (count($rows) < 2) { if (($GLOBALS['DEV_MODE']) && (count($rows) == 1)) { call_user_func('_multi_sort', current($rows), current($rows)); // Just to make sure there's no crash bug in the sort function } return; } $first_key = key($rows); if ((is_integer($first_key)) && (array_unique(array_map('is_integer', array_keys($rows))) === array(true))) { usort($rows, '_multi_sort'); } else { uasort($rows, '_multi_sort'); } } } /** * Do a user sort, preserving order where reordering not needed. Based on a PHP manual comment at http://php.net/manual/en/function.usort.php * * @param array $array Sort array * @param mixed $cmp_function Comparison function */ function merge_sort(&$array, $cmp_function = 'strcmp') { // Arrays of size<2 require no action. if (count($array) < 2) { if (($GLOBALS['DEV_MODE']) && (count($array) == 1)) { call_user_func($cmp_function, current($array), current($array)); // Just to make sure there's no crash bug in the sort function } return; } // Split the array in half $halfway = intval(floatval(count($array)) / 2.0); $array1 = array_slice($array, 0, $halfway); $array2 = array_slice($array, $halfway); // Recurse to sort the two halves merge_sort($array1, $cmp_function); merge_sort($array2, $cmp_function); // If all of $array1 is <= all of $array2, just append them. if (call_user_func($cmp_function, end($array1), reset($array2)) < 1) { $array = array_merge($array1, $array2); return; } // Merge the two sorted arrays into a single sorted array $array = array(); reset($array1); reset($array2); $ptr1 = 0; $ptr2 = 0; $cnt1 = count($array1); $cnt2 = count($array2); while (($ptr1 < $cnt1) && ($ptr2 < $cnt2)) { if (call_user_func($cmp_function, current($array1), current($array2)) < 1) { $key = key($array1); if (is_integer($key)) { $array[] = current($array1); } else { $array[$key] = current($array1); } $ptr1++; next($array1); } else { $key = key($array2); if (is_integer($key)) { $array[] = current($array2); } else { $array[$key] = current($array2); } $ptr2++; next($array2); } } // Merge the remainder while ($ptr1 < $cnt1) { $key = key($array1); if (is_integer($key)) { $array[] = current($array1); } else { $array[$key] = current($array1); } $ptr1++; next($array1); } while ($ptr2 < $cnt2) { $key = key($array2); if (is_integer($key)) { $array[] = current($array2); } else { $array[$key] = current($array2); } $ptr2++; next($array2); } } /** * Helper function to sort a list of maps by the value at $key in each of those maps. * * @param array $a The first to compare * @param array $b The second to compare * @return integer The comparison result (0 for equal, -1 for less, 1 for more) * @ignore */ function _multi_sort($a, $b) { global $M_SORT_KEY; $keys = explode(',', is_string($M_SORT_KEY) ? $M_SORT_KEY : strval($M_SORT_KEY)); $first_key = $keys[0]; if ($first_key[0] === '!') { $first_key = substr($first_key, 1); } if ((is_string($a[$first_key])) || (is_object($a[$first_key]))) { $ret = 0; do { $key = array_shift($keys); $backwards = ($key[0] === '!'); if ($backwards) { $key = substr($key, 1); } $av = $a[$key]; $bv = $b[$key]; if (is_object($av)) { $av = $av->evaluate(); } if (is_object($bv)) { $bv = $bv->evaluate(); } if ($backwards) { // Flip around if ((is_numeric($av)) && (is_numeric($bv))) { $ret = -strnatcasecmp($av, $bv); } else { $ret = -strcasecmp($av, $bv); } } else { if ((is_numeric($av)) && (is_numeric($bv))) { $ret = strnatcasecmp($av, $bv); } else { $ret = strcasecmp($av, $bv); } } } while ((count($keys) !== 0) && ($ret === 0)); return $ret; } do { $key = array_shift($keys); if ($key[0] === '!') { // Flip around $key = substr($key, 1); $ret = ($a[$key] > $b[$key]) ? -1 : (($a[$key] == $b[$key]) ? 0 : 1); } else { $ret = ($a[$key] > $b[$key]) ? 1 : (($a[$key] == $b[$key]) ? 0 : -1); } } while ((count($keys) !== 0) && ($ret == 0)); return $ret; } /** * Require all code relating to the Conversr forum */ function cns_require_all_forum_stuff() { require_lang('cns'); require_code('cns_members'); require_code('cns_topics'); require_code('cns_posts'); require_code('cns_moderation'); require_code('cns_groups'); require_code('cns_forums'); require_code('cns_general'); } /** * Create file with unique file name, but works around compatibility issues between servers. Note that the file is NOT automatically deleted. You should also delete it using "@unlink", as some servers have problems with permissions. * * @param string $prefix The prefix of the temporary file name. * @return ~string The name of the temporary file (false: error). */ function cms_tempnam($prefix = 'cms') { require_code('files2'); return _cms_tempnam($prefix); } /** * Peek at a stack element. * * @param array $array The stack to peek in * @param integer $depth_down The depth into the stack we are peaking * @return mixed The result of the peeking */ function array_peek($array, $depth_down = 1) { $count = count($array); if ($count - $depth_down < 0) { return null; } return $array[$count - $depth_down]; } /** * Make a value suitable for use in an XML ID. * * @param string $param The value to escape * @return string The escaped value */ function fix_id($param) { if (preg_match('#^[A-Za-z][\w]*$#', $param) !== 0) { return $param; // Optimisation } $length = strlen($param); $new = ''; for ($i = 0; $i < $length; $i++) { $char = $param[$i]; switch ($char) { case '[': $new .= '_opensquare_'; break; case ']': $new .= '_closesquare_'; break; case ''': case '\'': $new .= '_apostophe_'; break; case '-': $new .= '_minus_'; break; case ' ': $new .= '_space_'; break; case '+': $new .= '_plus_'; break; case '*': $new .= '_star_'; break; case '/': $new .= '__'; break; default: $ascii = ord($char); if ((($i !== 0) && ($char === '_')) || (($ascii >= 48) && ($ascii <= 57)) || (($ascii >= 65) && ($ascii <= 90)) || (($ascii >= 97) && ($ascii <= 122))) { $new .= $char; } else { $new .= '_' . strval($ascii) . '_'; } break; } } if ($new === '') { $new = 'zero_length'; } if ($new[0] === '_') { $new = 'und_' . $new; } return $new; } /** * See if the current URL matches the given Composr match-keys. * * @param mixed $match_keys Match keys (comma-separated list of match-keys, or array of) * @param boolean $support_post Check against POSTed data too * @param ?array $current_params Parameters to check against (null: get from environment GET/POST) - if set, $support_post is ignored) * @param ?ID_TEXT $current_zone_name Current zone name (null: get from environment) * @param ?ID_TEXT $current_page_name Current page name (null: get from environment) * @return boolean Whether there is a match */ function match_key_match($match_keys, $support_post = false, $current_params = null, $current_zone_name = null, $current_page_name = null) { $req_func = $support_post ? 'either_param_string' : 'get_param_string'; if ($current_zone_name === null) { global $IN_SELF_ROUTING_SCRIPT; if (!$IN_SELF_ROUTING_SCRIPT) { return false; } $current_zone_name = get_zone_name(); } if ($current_page_name === null) { $current_page_name = get_page_name(); } $potentials = is_array($match_keys) ? $match_keys : explode(',', $match_keys); foreach ($potentials as $potential) { $parts = is_array($potential) ? $potential : explode(':', $potential); if (($parts[0] == '_WILD') || ($parts[0] == '_SEARCH')) { $parts[0] = $current_zone_name; } if ((!isset($parts[1])) || ($parts[1] == '_WILD') || (($parts[1] == '_WILD_NOT_START') && ($current_page_name != get_zone_default_page($parts[0])))) { $parts[1] = $current_page_name; } if (($parts[0] == 'site') && (get_option('collapse_user_zones') == '1')) { $parts[0] = ''; } $zone_matches = (($parts[0] == $current_zone_name) || ((strpos($parts[0], '*') !== false) && (simulated_wildcard_match($current_zone_name, $parts[0], true)))); $page_matches = ((($parts[1] == '') && ($current_page_name == get_zone_default_page($current_zone_name))) || ($parts[1] == $current_page_name) || ((strpos($parts[1], '*') !== false) && (simulated_wildcard_match($current_page_name, $parts[1], true)))); if (($zone_matches) && ($page_matches)) { $bad = false; for ($i = 2; $i < count($parts); $i++) { if ($parts[$i] != '') { if (($i == 2) && (strpos($parts[$i], '=') === false)) { $parts[$i] = 'type=' . $parts[$i]; } elseif (($i == 3) && (strpos($parts[$i], '=') === false)) { $parts[$i] = 'id=' . $parts[$i]; } } $subparts = explode('=', $parts[$i]); if ($subparts[0] == 'type') { $default = 'browse'; } else { $default = ''; } if (count($subparts) != 2) { $bad = true; continue; } $env_val = ($current_params === null) ? call_user_func_array($req_func, array($subparts[0], null)) : (isset($current_params[$subparts[0]]) ? $current_params[$subparts[0]] : null); if ($subparts[1] == '_WILD') { if ($env_val !== null) { $subparts[1] = $env_val; // null won't match to a wildcard } } else { if ($env_val === null) { $env_val = $default; } } if ($env_val !== $subparts[1]) { $bad = true; continue; } } if (!$bad) { return true; } } } return false; } /** * Get the name of the page in the URL or active script. * * @return ID_TEXT The current page/script name */ function get_page_or_script_name() { global $IN_SELF_ROUTING_SCRIPT; if ($IN_SELF_ROUTING_SCRIPT) { return get_page_name(); } return current_script(); } /** * Get the name of the page in the URL (by convention: the current page). * This works on the basis of the 'page' parameter and does not require index.php be the active script. * It will do dash to underscore substitution as required. * * @return ID_TEXT The current page name */ function get_page_name() { global $PAGE_NAME_CACHE; if (isset($PAGE_NAME_CACHE)) { return $PAGE_NAME_CACHE; } global $ZONE, $GETTING_PAGE_NAME, $BOOTSTRAPPING; if ($GETTING_PAGE_NAME) { return 'unknown'; } $GETTING_PAGE_NAME = true; $page = get_param_string('page', '', true); if (strlen($page) > 80) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (($page == '') && ($ZONE !== null)) { $page = $ZONE['zone_default_page']; if ($page === null) { $page = ''; } } if (strpos($page, '..') !== false) { $page = filter_naughty($page); } $simplified_algorithm = $BOOTSTRAPPING; // fix_page_name_dashing calls request_page, which won't work reliably during bootstrapping if ($simplified_algorithm) { $page = str_replace('-', '_', $page); } else { $page = fix_page_name_dashing(get_zone_name(), $page); } if (!$GETTING_PAGE_NAME) { // It's been changed by process_url_monikers, which was called indirectly by fix_page_name_dashing return $PAGE_NAME_CACHE; } if (($ZONE !== null) && (!$simplified_algorithm)) { $PAGE_NAME_CACHE = $page; } $GETTING_PAGE_NAME = false; return $page; } /** * Fix a page name that may have been given dashes for SEO reasons. * * @param string $zone Zone. * @param string $page Page. * @return string The fixed page name. */ function fix_page_name_dashing($zone, $page) { if (strpos($page, '/') !== false) { return $page; // It's a moniker that hasn't been processed yet } // Fix page-name dashes if needed if (strpos($page, '-') !== false) { require_code('site'); $test = _request_page($page, $zone, null, null, true); if ($test === false) { $_page = str_replace('-', '_', $page); $test = _request_page($_page, $zone); if ($test !== false) { $page = $_page; } } } return $page; } /** * Take a list of maps, and make one of the values of each array the index of a map to the map. * * list_to_map is very useful for handling query results. * Let's imagine you get the result of SELECT id,title FROM sometable. * list_to_map turns the array of rows into a map between the id key and each row. * * @param string $map_value The key key of our maps that reside in our map * @param array $list The list of maps * @return array The collapsed map */ function list_to_map($map_value, $list) { $i = 0; $new_map = array(); foreach ($list as $map) { $key = $map[$map_value]; $new_map[$key] = $map; $i++; } if ($i > 0) { return $new_map; } return array(); } /** * Take a list of maps of just two elements, and make it into a single map * * @param string $key The key key of our maps that reside in our map * @param string $value The value key of our maps that reside in our map * @param array $list The map of maps * @return array The collapsed map */ function collapse_2d_complexity($key, $value, $list) { $new_map = array(); foreach ($list as $map) { $new_map[$map[$key]] = $map[$value]; } return $new_map; } /** * Take a list of maps of just one element, and make it into a single map * * @param ?string $key The key of our maps that reside in our map (null: first key) * @param array $list The map of maps * @return array The collapsed map */ function collapse_1d_complexity($key, $list) { $new_array = array(); foreach ($list as $map) { if ($key === null) { $new_array[] = array_shift($map); } else { $new_array[] = $map[$key]; } } return $new_array; } /** * Used by cms_strip_tags to handle whether to strip a tag. * * @param array $matches Array of matches * @return string Substituted tag text * * @ignore */ function _cms_strip_tags_callback($matches) { global $STRIP_TAGS_TAGS, $STRIP_TAGS_TAGS_AS_ALLOW; $tag_covered = stripos($STRIP_TAGS_TAGS, '<' . $matches[1] . '>'); if ((($STRIP_TAGS_TAGS_AS_ALLOW) && ($tag_covered !== false)) || ((!$STRIP_TAGS_TAGS_AS_ALLOW) && ($tag_covered === false))) { return $matches[0]; } return ''; } /** * Strip HTML and PHP tags from a string. * Equivalent to PHP's strip_tags, whose $allowable_tags parameter is expected to be deprecated in PHP 7.3 (https://wiki.php.net/rfc/deprecations_php_7_3). * * @param string $str Subject * @param string $tags Comma-separated list of tags * @param boolean $tags_as_allow Whether tags represents a whitelist (set for false to allow all by default and make $tags a blacklist) * @return string Result */ function cms_strip_tags($str, $tags, $tags_as_allow = true) { global $STRIP_TAGS_TAGS, $STRIP_TAGS_TAGS_AS_ALLOW; $STRIP_TAGS_TAGS = $tags; $STRIP_TAGS_TAGS_AS_ALLOW = $tags_as_allow; return preg_replace_callback('#</?([^\s<>]+)(\s[^<>]*)?' . '>#', '_cms_strip_tags_callback', $str); } /** * Find whether an IP address is valid * * @param IP $ip IP address to check. * @return boolean Whether the IP address is valid. */ function is_valid_ip($ip) { if ($ip == '') { return false; } $parts = array(); if ((strpos($ip, '.') !== false) && (preg_match('#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#', $ip, $parts) != 0)) { if (intval($parts[1]) > 255) { return false; } if (intval($parts[2]) > 255) { return false; } if (intval($parts[3]) > 255) { return false; } if (intval($parts[4]) > 255) { return false; } return true; } if ((strpos($ip, ':') !== false) && (preg_match('#^[\d:a-fA-F]*$#', $ip) != 0)) { return true; } return false; } /** * Attempt to get the clean IP address of the current user * * @param integer $amount The number of groups to include in the IP address (rest will be replaced with *'s). For IP6, this is doubled. * @set 1 2 3 4 * @param ?IP $ip IP address to use, normally left null (null: current user's) * @return IP The users IP address (blank: could not find a valid one) */ function get_ip_address($amount = 4, $ip = null) { require_code('config'); if ((get_value('cloudflare_workaround') === '1') && (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) && (isset($_SERVER['REMOTE_ADDR']))) { $regexp = '^(204\.93\.240\.|204\.93\.177\.|199\.27\.|173\.245\.|103\.21\.|103\.22\.|103\.31\.|141\.101\.|108\.162\.|190\.93\.|188\.114\.|197\.234\.|198\.41\.|162\.)'; if (preg_match('#' . $regexp . '#', $_SERVER['REMOTE_ADDR']) != 0) { $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR']; unset($_SERVER['HTTP_X_FORWARDED_FOR']); } } if ($ip === null) { /* Presents too many security and maintenance problems. Can easily be faked, or changed. $fw = cms_srv('HTTP_X_FORWARDED_FOR'); if (cms_srv('HTTP_CLIENT_IP') != '') { $fw = cms_srv('HTTP_CLIENT_IP'); } if (($fw != '') && ($fw != '127.0.0.1') && (substr($fw, 0, 8) != '192.168.') && (substr($fw, 0, 3) != '10.') && (is_valid_ip($fw)) && ($fw != cms_srv('SERVER_ADDR'))) { $ip = $fw; } else */ $ip = cms_srv('REMOTE_ADDR'); } global $SITE_INFO; if (($amount == 3) && (!empty($SITE_INFO['full_ips'])) && ($SITE_INFO['full_ips'] == '1')) { // Extra configurable security $amount = 4; } return normalise_ip_address($ip, $amount); } /** * Normalise a provided IP address * * @param IP $ip The IP address to normalise * @param ?integer $amount Amount to mask out (null: do not) * @return IP The normalised IP address */ function normalise_ip_address($ip, $amount = null) { $raw_ip = $ip; static $ip_cache = array(); if (isset($ip_cache[$raw_ip][$amount])) { return $ip_cache[$raw_ip][$amount]; } // Bizarro-filter (found "in the wild") $pos = strpos($ip, ','); if ($pos !== false) { $ip = substr($ip, 0, $pos); } // ...and another if (strpos($ip, '%') !== false) { $ip = preg_replace('#%14$#', '', $ip); } if (!is_valid_ip($ip)) { $ip_cache[$raw_ip][$amount] = ''; return ''; } // Normalise if (strpos($ip, '.') === false) { // IPv6 if (substr_count($ip, ':') < 7) { $ip = str_replace('::', str_repeat(':', (7 - substr_count($ip, ':')) + 2), $ip); } $parts = explode(':', $ip); for ($i = 0; $i < (is_null($amount) ? 8 : ($amount * 2)); $i++) { $parts[$i] = isset($parts[$i]) ? str_pad($parts[$i], 4, '0', STR_PAD_LEFT) : '0000'; } if (!is_null($amount)) { for ($i = $amount * 2; $i < 8; $i++) { $parts[$i] = '*'; } } $ip_cache[$raw_ip][$amount] = implode(':', $parts); } else { // IPv4 $parts = explode('.', $ip); for ($i = 0; $i < (is_null($amount) ? 4 : $amount); $i++) { if (!array_key_exists($i, $parts)) { $parts[$i] = '0'; } } if (!is_null($amount)) { for ($i = $amount; $i < 4; $i++) { $parts[$i] = '*'; } } $ip_cache[$raw_ip][$amount] = implode('.', $parts); } return $ip_cache[$raw_ip][$amount]; } /** * Exit with debug data, only for a specific IP address. * * @param IP $ip IP address of tester * @param mixed $data Data to display */ function me_debug($ip, $data) { if (get_ip_address() == $ip) { @var_dump($data); exit(); } } /** * Get a string of the users web browser * * @return string The web browser string */ function get_browser_string() { return cms_srv('HTTP_USER_AGENT'); } /** * Get the user's operating system * * @return string The operating system string */ function get_os_string() { if (cms_srv('HTTP_UA_OS') != '') { return cms_srv('HTTP_UA_OS'); } elseif (cms_srv('HTTP_USER_AGENT') != '') { // E.g. Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586) // We need to get the stuff in the brackets $matches = array(); if (preg_match('#\(([^\)]*)\)#', cms_srv('HTTP_USER_AGENT'), $matches) != 0) { $ret = $matches[1]; $ret = preg_replace('#^compatible; (MSIE[^;]*; )?#', '', $ret); return $ret; } } return ''; } /** * Find if Cron is installed * * @param boolean $absolutely_sure Whether Cron really needs to be installed (if set to false it will be assumed installed on dev-mode) * @return boolean Whether Cron is installed */ function cron_installed($absolutely_sure = false) { $test = get_param_integer('keep_has_cron', null); if ($test !== null) { return $test == 1; } if (!$absolutely_sure) { if ($GLOBALS['DEV_MODE']) { return true; } } $last_cron = get_value('last_cron'); if ($last_cron === null) { return false; } return intval($last_cron) > (time() - 60 * 60 * 5); } /** * Compare two IP addresses for potential correlation. Not as simple as equality due to '*' syntax. * * @param string $wild The general IP address that is potentially wildcarded * @param IP $full The specific IP address we are checking * @return boolean Whether the IP addresses correlate */ function compare_ip_address($wild, $full) { $wild_parts = explode((strpos($full, '.') !== false) ? '.' : ':', $wild); $full_parts = explode((strpos($full, '.') !== false) ? '.' : ':', $full); foreach ($wild_parts as $i => $wild_part) { if (($wild_part != '*') && ($wild_part != $full_parts[$i])) { return false; } } return true; } /** * Compare two IP addresses for potential correlation. Not as simple as equality due to '*' syntax. IP4-only variant * * @param string $wild The general IP address that is potentially wildcarded * @param array $full_parts The exploded parts of the specific IP address we are checking * @return boolean Whether the IP addresses correlate */ function compare_ip_address_ip4($wild, $full_parts) { $wild_parts = explode('.', $wild); foreach ($wild_parts as $i => $wild_part) { if (($wild_part != '*') && ($wild_part != $full_parts[$i])) { return false; } } return true; } /** * Compare two IP addresses for potential correlation. Not as simple as equality due to '*' syntax. IP6-only variant * * @param string $wild The general IP address that is potentially wildcarded * @param array $full_parts The exploded parts of the specific IP address we are checking * @return boolean Whether the IP addresses correlate */ function compare_ip_address_ip6($wild, $full_parts) { $wild_parts = explode(':', $wild); foreach ($wild_parts as $i => $wild_part) { if (($wild_part != '*') && ($wild_part != $full_parts[$i])) { return false; } } return true; } /** * Check to see if an IP address is banned. * * @param string $ip The IP address to check for banning * @param boolean $force_db Force check via database * @param boolean $handle_uncertainties Handle uncertainities (used for the external bans - if true, we may return null, showing we need to do an external check). Only works with $force_db. * @return ?boolean Whether the IP address is banned (null: unknown) */ function ip_banned($ip, $force_db = false, $handle_uncertainties = false) { // NB: This function will make the first query called, so we will be a bit smarter, checking for errors static $cache = array(); if ($handle_uncertainties) { if (array_key_exists($ip, $cache)) { return $cache[$ip]; } } if (!addon_installed('securitylogging')) { return false; } if ($ip == '') { return false; } // Check exclusions first $_exclusions = get_option('spam_check_exclusions'); $exclusions = explode(',', $_exclusions); foreach ($exclusions as $exclusion) { if (trim($ip) == $exclusion) { return false; } } global $SITE_INFO; if ((!$force_db) && (((isset($SITE_INFO['known_suexec'])) && ($SITE_INFO['known_suexec'] == '1')) || (is_writable_wrap(get_file_base() . '/.htaccess')))) { $bans = array(); $ban_count = preg_match_all('#\n(deny from|require not ip) (.*)#i', cms_file_get_contents_safe(get_file_base() . '/.htaccess'), $bans); $ip_bans = array(); for ($i = 0; $i < $ban_count; $i++) { $ip_bans[$bans[1][$i]] = array('ip' => $bans[1][$i]); } } else { $ip_bans = function_exists('persistent_cache_get') ? persistent_cache_get('IP_BANS') : null; if ($ip_bans === null) { $ip_bans = $GLOBALS['SITE_DB']->query_select('banned_ip', array('*'), null, '', null, null, true); if (!is_array($ip_bans)) { // LEGACY $ip_bans = $GLOBALS['SITE_DB']->query_select('usersubmitban_ip', array('*'), null, '', null, null, true); } if ($ip_bans !== null) { persistent_cache_set('IP_BANS', $ip_bans); } } if ($ip_bans === null) { critical_error('DATABASE_FAIL'); } } $ip4 = (strpos($ip, '.') !== false); if ($ip4) { $ip_parts = explode('.', $ip); } else { $ip_parts = explode(':', $ip); } $self_ip = null; foreach ($ip_bans as $ban) { if ((isset($ban['i_ban_until'])) && ($ban['i_ban_until'] < time())) { $GLOBALS['SITE_DB']->query('DELETE FROM ' . get_table_prefix() . 'banned_ip WHERE i_ban_until IS NOT NULL AND i_ban_until<' . strval(time())); continue; } if ((($ip4) && (compare_ip_address_ip4($ban['ip'], $ip_parts))) || ((!$ip4) && (compare_ip_address_ip6($ban['ip'], $ip_parts)))) { if ($self_ip === null) { $self_host = cms_srv('HTTP_HOST'); if (($self_host == '') || (preg_match('#^localhost[\.\:$]#', $self_host) != 0)) { $self_ip = ''; } else { $self_ip = cms_gethostbyname($self_host); if ($self_ip == $self_host) { $self_ip = cms_srv('SERVER_ADDR'); } } } if (($self_ip != '') && (compare_ip_address($ban['ip'], $self_ip))) { continue; } if (compare_ip_address($ban['ip'], '127.0.0.1')) { continue; } if (compare_ip_address($ban['ip'], 'fe00:0000:0000:0000:0000:0000:0000:0000')) { continue; } if (array_key_exists('i_ban_positive', $ban)) { $ret = ($ban['i_ban_positive'] == 1); } else { $ret = true; } if ($handle_uncertainties) { $cache[$ip] = $ret; } return $ret; } } $ret = $handle_uncertainties ? null : false; if ($handle_uncertainties) { $cache[$ip] = $ret; } return $ret; } /** * Log an action * * @param ID_TEXT $type The type of activity just carried out (a language string ID) * @param ?SHORT_TEXT $a The most important parameter of the activity (e.g. D) (null: none) * @param ?SHORT_TEXT $b A secondary (perhaps, human readable) parameter of the activity (e.g. caption) (null: none) * @return ?AUTO_LINK Log ID (null: did not save a log) */ function log_it($type, $a = null, $b = null) { require_code('global4'); return _log_it($type, $a, $b); } /** * Escape a string to fit within PHP double quotes. * * @param string $in String in * @return string Resultant string */ function php_addslashes($in) { global $PHP_REP_FROM, $PHP_REP_TO; return str_replace($PHP_REP_FROM, $PHP_REP_TO, $in); } /** * Remove any duplication inside the list of rows (each row being a map). Duplication is defined by rows with correspinding IDs. * * @param array $rows The rows to remove duplication of * @param string $id_field The ID field * @return array The filtered rows */ function remove_duplicate_rows($rows, $id_field = 'id') { $ids_seen = array(); $rows2 = array(); foreach ($rows as $row) { if (!array_key_exists($row[$id_field], $ids_seen)) { $rows2[] = $row; } $ids_seen[$row[$id_field]] = true; } return $rows2; } /** * Update the member tracker for the currently viewing user. * * @param ID_TEXT $page The page * @param ID_TEXT $type The type * @param ID_TEXT $id The ID */ function member_tracking_update($page, $type, $id) { if (get_value('no_member_tracking') === '1') { return; } if (!$GLOBALS['SITE_DB']->table_is_locked('member_tracking')) { $GLOBALS['SITE_DB']->query('DELETE FROM ' . get_table_prefix() . 'member_tracking WHERE mt_time<' . strval(time() - 60 * intval(get_option('users_online_time'))) . ' OR (mt_member_id=' . strval(get_member()) . ' AND ' . db_string_equal_to('mt_type', $type) . ' AND ' . db_string_equal_to('mt_id', $id) . ' AND ' . db_string_equal_to('mt_page', $page) . ')'); } $GLOBALS['SITE_DB']->query_insert('member_tracking', array( 'mt_member_id' => get_member(), 'mt_cache_username' => $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true), 'mt_time' => time(), 'mt_page' => $page, 'mt_type' => $type, 'mt_id' => $id ), false, true); // Ignore errors for race conditions } /** * Find whether the current user is invisible. * * @return boolean Whether the current user is invisible */ function is_invisible() { global $SESSION_CACHE; $s = get_session_id(); return ((isset($SESSION_CACHE[$s])) && ($SESSION_CACHE[$s]['session_invisible'] == 1)); } /** * Get the number of users on the site in the last 5 minutes. The function also maintains the statistic via the sessions table. * * @return integer The number of users on the site */ function get_num_users_site() { if (get_value('disable_user_online_counting') === '1') { return 1; } global $NUM_USERS_SITE_CACHE, $PEAK_USERS_EVER_CACHE, $PEAK_USERS_WEEK_CACHE; $users_online_time_seconds = 60 * intval(get_option('users_online_time')); $NUM_USERS_SITE_CACHE = get_value_newer_than('users_online', time() - $users_online_time_seconds / 2); /* Refreshes half way through the user online time, to approximate accuracy */ if ($NUM_USERS_SITE_CACHE === null) { $NUM_USERS_SITE_CACHE = get_value('users_online'); $count = 0; require_code('users2'); get_users_online(false, null, $count); $NUM_USERS_SITE_CACHE = strval($count); if (!$GLOBALS['SITE_DB']->table_is_locked('values')) { set_value('users_online', $NUM_USERS_SITE_CACHE); } } if ((intval($NUM_USERS_SITE_CACHE) > intval(get_option('maximum_users'))) && (intval(get_option('maximum_users')) > 1) && (get_page_name() != 'login') && (!has_privilege(get_member(), 'access_overrun_site')) && (!running_script('cron_bridge'))) { set_http_status_code('503'); critical_error('BUSY', do_lang('TOO_MANY_USERS')); } if (addon_installed('stats')) { // Store a peak record if there is one $PEAK_USERS_EVER_CACHE = get_value('user_peak'); if (($PEAK_USERS_EVER_CACHE === null) || ($PEAK_USERS_EVER_CACHE == '')) { $_peak_users_user = $GLOBALS['SITE_DB']->query_select_value_if_there('usersonline_track', 'MAX(peak)', null, '', true); $PEAK_USERS_EVER_CACHE = ($_peak_users_user === null) ? $NUM_USERS_SITE_CACHE : strval($_peak_users_user); if (!$GLOBALS['SITE_DB']->table_is_locked('values')) { set_value('user_peak', $PEAK_USERS_EVER_CACHE); } } if (intval($NUM_USERS_SITE_CACHE) > intval($PEAK_USERS_EVER_CACHE)) { // New record $GLOBALS['SITE_DB']->query_insert('usersonline_track', array('date_and_time' => time(), 'peak' => intval($NUM_USERS_SITE_CACHE)), false, true); if (!$GLOBALS['SITE_DB']->table_is_locked('values')) { set_value('user_peak', $NUM_USERS_SITE_CACHE); } } // Store a 7-day-cycle peak record if we've made one $PEAK_USERS_WEEK_CACHE = get_value_newer_than('user_peak_week', time() - $users_online_time_seconds / 2); $store_anyway = false; if (($PEAK_USERS_WEEK_CACHE === null) || ($PEAK_USERS_WEEK_CACHE == '')) { $store_anyway = true; } if ((intval($NUM_USERS_SITE_CACHE) > intval($PEAK_USERS_WEEK_CACHE)) || ($store_anyway)) { $PEAK_USERS_WEEK_CACHE = $NUM_USERS_SITE_CACHE; // But also delete anything else in the last 7 days that was less than the new weekly peak record, to keep the stats clean (we only want 7 day peaks to be stored) $GLOBALS['SITE_DB']->query('DELETE FROM ' . get_table_prefix() . 'usersonline_track WHERE date_and_time>' . strval(time() - 60 * 60 * 24 * 7) . ' AND peak<=' . $PEAK_USERS_WEEK_CACHE, null, null, true); // Set record for week set_value('user_peak_week', $PEAK_USERS_WEEK_CACHE); $GLOBALS['SITE_DB']->query_insert('usersonline_track', array('date_and_time' => time(), 'peak' => intval($PEAK_USERS_WEEK_CACHE)), false, true); } } return intval($NUM_USERS_SITE_CACHE); } /** * Get the largest amount of users ever to be on the site at the same time. * * @return integer The number of peak users */ function get_num_users_peak() { global $PEAK_USERS_EVER_CACHE; return intval($PEAK_USERS_EVER_CACHE); } /** * Get the specified string, but with all characters escaped. * * @param mixed $string The input string * @return string The escaped string */ function escape_html($string) { //if ($string === '') return $string; // Optimisation, but doesn't work well if (isset($string->codename)/*faster than is_object*/) { return $string; } /*if ($GLOBALS['XSS_DETECT']) { Useful for debugging if (ocp_is_escaped($string)) { @var_dump(debug_backtrace()); @exit('String double-escaped'); } }*/ global $XSS_DETECT, $ESCAPE_HTML_OUTPUT, $DECLARATIONS_STATE; $ret = @htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, get_charset()); if (defined('I_UNDERSTAND_XSS') && !$DECLARATIONS_STATE[I_UNDERSTAND_XSS]) { $ESCAPE_HTML_OUTPUT[$ret] = true; } if ($XSS_DETECT) { ocp_mark_as_escaped($ret); } return $ret; } /** * See's if the current browser matches some special property code. Assumes users are keeping up on newish browsers (except for IE users, who are 6+) * * @param string $code The property code * @set android ios wysiwyg windows mac linux odd_os mobile ie ie8 ie8+ ie9 ie9+ gecko safari odd_browser chrome bot simplified_attachments_ui itunes * @param ?string $comcode Comcode that might be WYSIWYG edited; used to determine whether WYSIWYG may load when we'd prefer it to not do so (null: none) * @return boolean Whether there is a match */ function browser_matches($code, $comcode = null) { global $BROWSER_MATCHES_CACHE; if (isset($BROWSER_MATCHES_CACHE[$code])) { return $BROWSER_MATCHES_CACHE[$code]; } $browser = strtolower(cms_srv('HTTP_USER_AGENT')); $os = strtolower(cms_srv('HTTP_UA_OS')) . ' ' . $browser; $is_safari = strpos($browser, 'applewebkit') !== false; $is_chrome = strpos($browser, 'chrome/') !== false; $is_gecko = (strpos($browser, 'gecko') !== false) && !$is_safari; $is_ie = ((strpos($browser, 'msie') !== false) || (strpos($browser, 'trident') !== false) || (strpos($browser, 'edge/') !== false)); $is_ie8 = (strpos($browser, 'msie 8') !== false) && ($is_ie); $is_ie9 = (strpos($browser, 'msie 9') !== false) && ($is_ie); $is_ie8_plus = $is_ie; // Below IE8 not supported/recognised $is_ie9_plus = $is_ie && !$is_ie8; switch ($code) { case 'simplified_attachments_ui': $BROWSER_MATCHES_CACHE[$code] = !$is_ie8 && !$is_ie9 && get_option('simplified_attachments_ui') == '1' && get_option('complex_uploader') == '1' && has_js(); return $BROWSER_MATCHES_CACHE[$code]; case 'itunes': $BROWSER_MATCHES_CACHE[$code] = (get_param_integer('itunes', 0) == 1) || (strpos($browser, 'itunes') !== false); return $BROWSER_MATCHES_CACHE[$code]; case 'bot': $BROWSER_MATCHES_CACHE[$code] = (get_bot_type() !== null); return $BROWSER_MATCHES_CACHE[$code]; case 'android': $BROWSER_MATCHES_CACHE[$code] = strpos($browser, 'android') !== false; return $BROWSER_MATCHES_CACHE[$code]; case 'ios': $BROWSER_MATCHES_CACHE[$code] = strpos($browser, 'iphone') !== false || strpos($browser, 'ipad') !== false; return $BROWSER_MATCHES_CACHE[$code]; case 'wysiwyg': if ((get_option('wysiwyg') == '0') || ((is_mobile()) && ((is_null($comcode)) || (strpos($comcode, 'html]') === false)))) { $BROWSER_MATCHES_CACHE[$code] = false; return false; } $BROWSER_MATCHES_CACHE[$code] = (strpos($browser, 'android') === false); // Using CKEditor, which does not yet support Android return $BROWSER_MATCHES_CACHE[$code]; case 'windows': $BROWSER_MATCHES_CACHE[$code] = (strpos($os, 'windows') !== false) || (strpos($os, 'win32') !== false); return $BROWSER_MATCHES_CACHE[$code]; case 'mac': $BROWSER_MATCHES_CACHE[$code] = strpos($os, 'mac') !== false; return $BROWSER_MATCHES_CACHE[$code]; case 'linux': $BROWSER_MATCHES_CACHE[$code] = strpos($os, 'linux') !== false; return $BROWSER_MATCHES_CACHE[$code]; case 'odd_os': $BROWSER_MATCHES_CACHE[$code] = (strpos($os, 'windows') === false) && (strpos($os, 'mac') === false) && (strpos($os, 'linux') === false); return $BROWSER_MATCHES_CACHE[$code]; case 'mobile': $BROWSER_MATCHES_CACHE[$code] = is_mobile(); return $BROWSER_MATCHES_CACHE[$code]; case 'ie': $BROWSER_MATCHES_CACHE[$code] = $is_ie; return $BROWSER_MATCHES_CACHE[$code]; case 'ie8': $BROWSER_MATCHES_CACHE[$code] = $is_ie8; return $BROWSER_MATCHES_CACHE[$code]; case 'ie8+': $BROWSER_MATCHES_CACHE[$code] = $is_ie8_plus; return $BROWSER_MATCHES_CACHE[$code]; case 'ie9': $BROWSER_MATCHES_CACHE[$code] = $is_ie9; return $BROWSER_MATCHES_CACHE[$code]; case 'ie9+': $BROWSER_MATCHES_CACHE[$code] = $is_ie9_plus; return $BROWSER_MATCHES_CACHE[$code]; case 'chrome': $BROWSER_MATCHES_CACHE[$code] = $is_chrome; return $BROWSER_MATCHES_CACHE[$code]; case 'gecko': $BROWSER_MATCHES_CACHE[$code] = $is_gecko; return $BROWSER_MATCHES_CACHE[$code]; case 'safari': $BROWSER_MATCHES_CACHE[$code] = $is_safari; return $BROWSER_MATCHES_CACHE[$code]; case 'odd_browser': $BROWSER_MATCHES_CACHE[$code] = !$is_safari && !$is_gecko && !$is_ie; return $BROWSER_MATCHES_CACHE[$code]; } // Should never get here return false; } /** * Look at the user's browser, and decide if they are viewing on a mobile device or not. * * @param ?string $user_agent The user agent (null: get from environment, current user's browser) * @param boolean $truth Whether to always tell the truth (even if the current page does not have mobile support) * @return boolean Whether the user is using a mobile device */ function is_mobile($user_agent = null, $truth = false) { $user_agent_given = ($user_agent !== null); global $IS_MOBILE_CACHE, $IS_MOBILE_TRUTH_CACHE; if (!$user_agent_given) { if (($truth ? $IS_MOBILE_TRUTH_CACHE : $IS_MOBILE_CACHE) !== null) { return $truth ? $IS_MOBILE_TRUTH_CACHE : $IS_MOBILE_CACHE; } } if ((!function_exists('get_option')) || (get_option('mobile_support') == '0')) { if (function_exists('get_option')) { $IS_MOBILE_CACHE = false; $IS_MOBILE_TRUTH_CACHE = false; } return false; } if ($user_agent === null) { $user_agent = cms_srv('HTTP_USER_AGENT'); } global $SITE_INFO; if (((!isset($SITE_INFO['assume_full_mobile_support'])) || ($SITE_INFO['assume_full_mobile_support'] != '1')) && (isset($GLOBALS['FORUM_DRIVER'])) && (!$truth) && (running_script('index')) && (($theme = $GLOBALS['FORUM_DRIVER']->get_theme()) != 'default')) { $ini_path = (($theme == 'default' || $theme == 'admin') ? get_file_base() : get_custom_file_base()) . '/themes/' . $theme . '/theme.ini'; if (is_file($ini_path)) { $page = get_param_string('page', ''); // We intentionally do not use get_page_name, as that requires URL Monikers to work, which are not available early in boot (as needed by static cache) require_code('files'); $details = better_parse_ini_file($ini_path); if (!empty($details['mobile_pages'])) { if (substr($details['mobile_pages'], 0, 1) == '#' && substr($details['mobile_pages'], -1) == '#') { if (preg_match($details['mobile_pages'], get_zone_name() . ':' . $page) == 0) { $IS_MOBILE_CACHE = false; return false; } } else { if (preg_match('#(^|,)\s*' . preg_quote($page, '#') . '\s*(,|$)#', $details['mobile_pages']) == 0 && preg_match('#(^|,)\s*' . preg_quote(get_zone_name() . ':' . $page, '#') . '\s*(,|$)#', $details['mobile_pages']) == 0) { $IS_MOBILE_CACHE = false; return false; } } } } } if (!$user_agent_given && !$truth) { $val = get_param_integer('keep_mobile', null); if ($val !== null) { $result = ($val == 1); if (isset($GLOBALS['FORUM_DRIVER'])) { if ($truth) { $IS_MOBILE_TRUTH_CACHE = $result; } else { $IS_MOBILE_CACHE = $result; } } return $result; } } // The set of browsers (also change in static_cache.php) $browsers = array( // Implication by technology claims 'WML', 'WAP', 'Wap', 'MIDP', // Mobile Information Device Profile // Generics 'Mobile', 'Smartphone', 'WebTV', // Well known/important browsers/brands 'Mobile Safari', // Usually Android 'Android', 'iPhone', 'iPod', 'Opera Mobi', 'Opera Mini', 'BlackBerry', 'Windows Phone', 'nook browser', // Barnes and Noble ); $exceptions = array( 'iPad', ); if (((!isset($SITE_INFO['no_extra_mobiles'])) || ($SITE_INFO['no_extra_mobiles'] != '1')) && (is_file(get_file_base() . '/text_custom/mobile_devices.txt'))) { require_code('files'); $mobile_devices = better_parse_ini_file((get_file_base() . '/text_custom/mobile_devices.txt')); foreach ($mobile_devices as $key => $val) { if ($val == 1) { $browsers[] = $key; } else { $exceptions[] = $key; } } } // The test $result = (preg_match('/(' . implode('|', $browsers) . ')/i', $user_agent) != 0) && (preg_match('/(' . implode('|', $exceptions) . ')/i', $user_agent) == 0); if (!$user_agent_given) { if (isset($GLOBALS['FORUM_DRIVER'])) { if ($truth) { $IS_MOBILE_TRUTH_CACHE = $result; } else { $IS_MOBILE_CACHE = $result; } } } return $result; } /** * Get the name of a webcrawler bot, or null if no bot detected * * @param ?string $agent User agent (null: read from environment) * @return ?string Webcrawling bot name (null: not a bot) */ function get_bot_type($agent = null) { $agent_given = ($agent !== null); if (!$agent_given) { global $BOT_TYPE_CACHE; if ($BOT_TYPE_CACHE !== false) { return $BOT_TYPE_CACHE; } $agent = cms_srv('HTTP_USER_AGENT'); } if (strpos($agent, 'WebKit') !== false || strpos($agent, 'Trident') !== false || strpos($agent, 'MSIE') !== false || strpos($agent, 'Firefox') !== false || strpos($agent, 'Opera') !== false) { if (strpos($agent, 'bot') === false) { // Quick exit path if (!$agent_given) { $BOT_TYPE_CACHE = null; } return null; } } $agent = strtolower($agent); global $BOT_MAP_CACHE, $SITE_INFO; if ($BOT_MAP_CACHE === null) { if (((!isset($SITE_INFO['no_extra_bots'])) || ($SITE_INFO['no_extra_bots'] != '1')) && (is_file(get_file_base() . '/text_custom/bots.txt'))) { require_code('files'); $BOT_MAP_CACHE = better_parse_ini_file(get_file_base() . '/text_custom/bots.txt'); } else { $BOT_MAP_CACHE = array( 'zyborg' => 'Looksmart', 'googlebot' => 'Google', 'mediapartners-google' => 'Google Adsense', 'teoma' => 'Teoma', 'jeeves' => 'Ask Jeeves', 'ultraseek' => 'Infoseek', 'ia_archiver' => 'Alexa/Archive.org', 'msnbot' => 'Bing', 'bingbot' => 'Bing', 'mantraagent' => 'LookSmart', 'wisenutbot' => 'Looksmart', 'paros' => 'Paros', 'sqworm' => 'Aol.com', 'baidu' => 'Baidu', 'facebookexternalhit' => 'Facebook', 'yandex'=> 'Yandex', 'daum' => 'Daum', 'ahrefsbot' => 'Ahrefs', 'mj12bot' => 'Majestic-12', 'blexbot' => 'webmeup', 'duckduckbot' => 'DuckDuckGo', ); } } foreach ($BOT_MAP_CACHE as $id => $name) { if ($name == '') { continue; } if (strpos($agent, $id) !== false) { if (!$agent_given) { $BOT_TYPE_CACHE = $name; } return $name; } } if ((strpos($agent, 'bot') !== false) || (strpos($agent, 'spider') !== false)) { $to_a = strpos($agent, ' '); if ($to_a === false) { $to_a = strlen($agent); } $to_b = strpos($agent, '/'); if ($to_b === false) { $to_b = strlen($agent); } $name = substr($agent, 0, min($to_a, $to_b)); if (!$agent_given) { $BOT_TYPE_CACHE = $name; } return $name; } if (!$agent_given) { $BOT_TYPE_CACHE = null; } return null; } /** * Determine whether the user's browser supports cookies or not. * Unfortunately this function will only return true once a user has been to the site more than once... Composr will set a cookie, and if it perseveres, that indicates cookies work. * * @return boolean Whether the user has definitely got cookies */ function has_cookies() // Will fail on users first visit, but then will catch on { global $HAS_COOKIES_CACHE; if ($HAS_COOKIES_CACHE !== null) { return $HAS_COOKIES_CACHE; } /*if (($GLOBALS['DEV_MODE']) && (get_param_integer('keep_debug_has_cookies', 0) == 0) && (!running_script('commandr'))) We know this works by now, was tested for years. Causes annoyance when developing { $_COOKIE = array(); return false; }*/ if (isset($_COOKIE['has_cookies'])) { $HAS_COOKIES_CACHE = true; return true; } require_code('users_active_actions'); cms_setcookie('has_cookies', '1'); $HAS_COOKIES_CACHE = false; return false; } /** * Determine whether the user's browser supports JavaScript or not. * Unfortunately this function will only return true once a user has been to the site more than once... JavaScript will set a cookie, indicating it works. * * @return boolean Whether the user has definitely got JavaScript */ function has_js() { if (!function_exists('get_option')) { return true; } if (get_option('detect_javascript') == '0') { return true; } if (get_param_integer('keep_has_js', 0) == 1) { return true; } if (get_param_integer('keep_has_js', null) === 0) { return false; } return ((array_key_exists('js_on', $_COOKIE)) && ($_COOKIE['js_on'] == '1')); } /** * Turn an array into a humanely readable string. * * @param array $array Array to convert * @param boolean $already_stripped Whether PHP magic-quotes have already been cleaned out for the array * @return string A humanely readable version of the array. */ function flatten_slashed_array($array, $already_stripped = false) { $ret = ''; foreach ($array as $key => $val) { if (is_array($val)) { $val = flatten_slashed_array($val); } if (!$already_stripped && get_magic_quotes_gpc()) { $val = stripslashes($val); } $ret .= '<param>' . (is_integer($key) ? strval($key) : $key) . '=' . $val . '</param>' . "\n"; // $key may be integer, due to recursion line for list fields, above } return $ret; } /** * Get a word-filtered version of the specified text. * * @param string $text Text to filter * @return string Filtered version of the input text */ function wordfilter_text($text) { if (!addon_installed('wordfilter')) { return $text; } require_code('wordfilter'); return check_wordfilter($text, null, true); } /** * Assign this to explicitly declare that a variable may be of mixed type, and initialise to null. * * @return ?mixed Of mixed type (null: default) */ function mixed() { return null; } /** * Get meta information for specified resource * * @param ID_TEXT $type The type of resource (e.g. download) * @param ID_TEXT $id The ID of the resource * @return array A pair: The first element is the meta keyword string for the specified resource, and the other is the meta description string. */ function seo_meta_get_for($type, $id) { $cache = function_exists('persistent_cache_get') ? persistent_cache_get(array('seo', $type, $id)) : null; if ($cache !== null) { return $cache; } $where = array('meta_for_type' => $type, 'meta_for_id' => $id); $cache = array('', ''); $rows = $GLOBALS['SITE_DB']->query_select('seo_meta_keywords', array('meta_keyword'), $where, 'ORDER BY id'); foreach ($rows as $row) { if ($cache[0] != '') { $cache[0] .= ','; } $cache[0] .= get_translated_text($row['meta_keyword']); } $rows = $GLOBALS['SITE_DB']->query_select('seo_meta', array('meta_description'), $where, '', 1); if (array_key_exists(0, $rows)) { $cache[1] = get_translated_text($rows[0]['meta_description']); } persistent_cache_set(array('seo', $type, $id), $cache); return $cache; } /** * Load the specified resource's meta information into the system for use on this page. * Also, if the title is specified then this is used for the page title. * * @sets_output_state * * @param ID_TEXT $type The type of resource (e.g. download) * @param ID_TEXT $id The ID of the resource * @param ?string $title The page-specific title to use, in Comcode or plain-text format with possible HTML entities included [Comcode will later be stripped] (null: none) */ function seo_meta_load_for($type, $id, $title = null) { if (!$GLOBALS['IS_VIRTUALISED_REQUEST']) { $result = seo_meta_get_for($type, $id); global $SEO_KEYWORDS, $SEO_DESCRIPTION, $SHORT_TITLE; if ($result[0] != '') { $SEO_KEYWORDS = array_map('trim', explode(',', trim($result[0], ','))); } if ($result[1] != '') { $SEO_DESCRIPTION = $result[1]; } if ($title !== null) { set_short_title(str_replace('–', '-', str_replace('©', '(c)', str_replace(''', '\'', $title)))); } } // Otherwise don't bother (this is an optimisation) } /** * Get Tempcode for tags, based on loaded up from SEO keywords (seo_meta_load_for). * * @param ?ID_TEXT $limit_to The search code for this tag content (e.g. downloads) (null: there is none) * @param ?array $the_tags Explicitly pass a list of tags instead (null: use loaded ones) * @return Tempcode Loaded tag output (or blank if there are none) */ function get_loaded_tags($limit_to = null, $the_tags = null) { if (get_value('no_tags') === '1') { return new Tempcode(); } if (!addon_installed('search')) { return new Tempcode(); } if ($the_tags === null) { global $SEO_KEYWORDS; $the_tags = $SEO_KEYWORDS; } $tags = array(); if ($the_tags !== null) { $search_limiter_no = array('all_defaults' => '1'); if ($limit_to !== null) { $search_limiter_no['search_' . $limit_to] = '1'; $search_limiter_no['all_defaults'] = '0'; } if ($limit_to !== null) { $search_limiter_yes = array(); $search_limiter_yes['search_' . $limit_to] = '1'; $search_limiter_yes['all_defaults'] = '0'; } else { $search_limiter_yes = $search_limiter_no; } foreach ($the_tags as $tag) { $tag = trim($tag); if ($tag == '') { continue; } $tags[] = array( 'TAG' => $tag, 'LINK_LIMITEDSCOPE' => build_url(array('page' => 'search', 'type' => 'results', 'content' => '"' . $tag . '"', 'only_search_meta' => '1') + $search_limiter_yes, get_module_zone('search')), 'LINK_FULLSCOPE' => build_url(array('page' => 'search', 'type' => 'results', 'content' => '"' . $tag . '"', 'only_search_meta' => '1') + $search_limiter_no, get_module_zone('search')), ); } } return do_template('TAGS', array('_GUID' => '2cd542a245bc7d1c3f10e858e8fc5159', 'TAGS' => $tags, 'TYPE' => ($limit_to === null) ? '' : $limit_to)); } /** * Get the default page for a zone. * * @param ID_TEXT $zone_name Zone name * @return ID_TEXT Default page */ function get_zone_default_page($zone_name) { if ($zone_name == '_SELF') { $zone_name = get_zone_name(); } /*$p_test = function_exists('persistent_cache_get') ? persistent_cache_get(array('ZONE', $zone_name)) : null; Better to get from ALL_ZONES_TITLED, less cache volume if ($p_test !== null) { return $p_test['zone_default_page']; }*/ global $ZONE; if (($ZONE['zone_name'] == $zone_name) && ($ZONE['zone_default_page'] !== null)) { return $ZONE['zone_default_page']; } else { global $ZONE_DEFAULT_PAGES_CACHE; if (!isset($ZONE_DEFAULT_PAGES_CACHE[$zone_name])) { $_zone_default_page = null; if (function_exists('persistent_cache_get')) { $temp = persistent_cache_get('ALL_ZONES_TITLED'); if ($temp !== null) { $_zone_default_page = array(); foreach ($temp as $_temp) { list($_zone_name, , $zone_default_page) = $_temp; $_zone_default_page[] = array('zone_name' => $_zone_name, 'zone_default_page' => $zone_default_page); } } } if ($_zone_default_page === null) { $_zone_default_page = $GLOBALS['SITE_DB']->query_select('zones', array('zone_name', 'zone_default_page'), null/*Load multiple so we can cache for performance array('zone_name' => $zone_name)*/, 'ORDER BY zone_title', 50/*reasonable limit; zone_title is sequential for default zones*/); } $ZONE_DEFAULT_PAGES_CACHE[$zone_name] = 'start'; $ZONE_DEFAULT_PAGES_CACHE['collaboration'] = 'start'; // Set this in case collaboration zone removed but still referenced. Performance tweak! foreach ($_zone_default_page as $zone_row) { $ZONE_DEFAULT_PAGES_CACHE[$zone_row['zone_name']] = $zone_row['zone_default_page']; } } return $ZONE_DEFAULT_PAGES_CACHE[$zone_name]; } } /** * Turn a boring codename, into a "pretty" title. * * @param ID_TEXT $boring The codename * @return string The title */ function titleify($boring) { $ret = $boring; if (strpos($ret, '/') !== false || strpos($ret, '\\') !== false) { $ret = preg_replace('#([/\\\\])#', '${1} ', $ret); } $ret = ucwords(trim(str_replace('_', ' ', $boring))); $acronyms = array( 'CMS', 'CNS', 'URL', 'ID', 'UI', 'HTML', 'MSN', 'LDAP', 'SMS', 'SSL', 'XML', 'HPHP', 'CSS', 'SEO', 'JavaScript', ); foreach ($acronyms as $acronym) { if (stripos($ret, $acronym) !== false) { $ret = cms_preg_replace_safe('#(^|\s)' . preg_quote($acronym, '#') . '(\s|$)#i', '$1' . $acronym . '$2', $ret); } } if (strpos($ret, 'Ecommerce') !== false) { $ret = str_replace('Ecommerce', addon_installed('ecommerce') ? do_lang('ecommerce:ECOMMERCE') : 'eCommerce', $ret); } if (strpos($ret, 'Cpfs') !== false) { $ret = str_replace('Cpfs', do_lang('cns:CUSTOM_PROFILE_FIELDS'), $ret); } if (strpos($ret, 'Captcha') !== false) { $ret = str_replace('Captcha', addon_installed('captcha') ? do_lang('captcha:CAPTCHA') : 'CAPTCHA', $ret); } $ret = str_replace('Adminzone', do_lang('ADMIN_ZONE'), $ret); $ret = str_replace('Emails', do_lang('EMAILS'), $ret); $ret = str_replace('Phpinfo', 'PHP-Info', $ret); $ret = str_replace('CNS', 'Conversr', $ret); if (strpos($ret, 'Default Set') !== false) { $ret = str_replace('Default Set/cartoons', do_lang('cns:AVATARS_CARTOONS'), $ret); $ret = str_replace('Default Set/thematic', do_lang('cns:AVATARS_THEMATIC'), $ret); $ret = str_replace('Default Set', do_lang('cns:AVATARS_MISC'), $ret); } if ($GLOBALS['XSS_DETECT'] && ocp_is_escaped($boring)) { ocp_mark_as_escaped($ret); } return $ret; } /** * Propagate Filtercode through links. * * @param ID_TEXT $prefix Prefix for main filter environment variable * @return array Extra URL mappings */ function propagate_filtercode($prefix = '') { $active_filter = either_param_string(($prefix == '') ? 'active_filter' : ($prefix . '_active_filter'), ''); $map = array(); if ($active_filter != '') { $map['active_filter'] = $active_filter; foreach (array_keys($_GET + $_POST) as $key) { if (substr($key, 0, 7) == 'filter_') { $map[$key] = either_param_string($key, ''); } } } return $map; } /** * Propagate Filtercode through page-links. * * @return string Extra page-link mappings */ function propagate_filtercode_page_link() { $map = propagate_filtercode(); $_map = ''; foreach ($map as $key => $val) { $_map .= ':' . $key . '=' . urlencode($val); } return $_map; } /** * Make some text fractionably editable (i.e. inline editable). * * @param ID_TEXT $content_type Content type * @param mixed $id Content ID * @param mixed $title Content title (either unescaped string, or Compiled Comcode [i.e. Tempcode]) * @return Tempcode Inline editable HTML to put into output */ function make_fractionable_editable($content_type, $id, $title) { require_code('content'); $ob = get_content_object($content_type); $info = $ob->info(); $parameters = array( is_object($title) ? $title->evaluate() : $title, array_key_exists('edit_page_link_field', $info) ? $info['edit_page_link_field'] : preg_replace('#^\w\w?_#', '', array_key_exists('title_field_post', $info) ? $info['title_field_post'] : $info['title_field']), array_key_exists('edit_page_link_pattern_post', $info) ? str_replace('_WILD', is_integer($id) ? strval($id) : $id, $info['edit_page_link_pattern_post']) : preg_replace('#:_(.*)#', ':__${1}', str_replace('_WILD', is_integer($id) ? strval($id) : $id, $info['edit_page_link_pattern'])), (array_key_exists('title_field_supports_comcode', $info) && $info['title_field_supports_comcode']) ? '1' : '0', ); return directive_tempcode('FRACTIONAL_EDITABLE', is_object($title) ? $title : escape_html($title), $parameters); } /** * Find whether a fractional edit is underway. * * @return boolean Whether a fractional edit is underway */ function fractional_edit() { return post_param_integer('fractional_edit', 0) == 1; } /** * Convert some HTML to plain text. * * @param string $in HTML * @return string Plain text */ function strip_html($in) { if ((strpos($in, '<') === false) && (strpos($in, '&') === false)) { return $in; // Optimisation } $search = array( '#<script[^>]*?' . '>.*?</script>#si', // Strip out JavaScript '#<style[^>]*?' . '>.*?</style>#siU', // Strip style tags properly '#<![\s\S]*?--[ \t\n\r]*>#', // Strip multi-line comments including CDATA ); $in = preg_replace($search, '', $in); if (get_charset() != 'utf-8') { $in = str_replace(array('–', '—', '·', '“', '”', '‘', '’'), array('-', '-', '|', '"', '"', "'", "'"), $in); } $in = str_replace('><', '> <', $in); $in = strip_tags($in); return @html_entity_decode($in, ENT_QUOTES, get_charset()); } /** * Find the base URL for documentation. * * @return URLPATH The base URL for documentation */ function get_brand_base_url() { $value = function_exists('get_value') ? get_value('rebrand_base_url') : null; if (($value === null) || ($value == '')) { $value = 'http://compo.sr'; } return $value; } /** * Get a URL to a Composr tutorial. * * @param ?ID_TEXT $tutorial Name of a tutorial (null: don't include the page part) * @return URLPATH URL to a tutorial */ function get_tutorial_url($tutorial) { $ret = get_brand_page_url(array('page' => is_null($tutorial) ? 'abcdef' : $tutorial), 'docs' . strval(cms_version())); if (is_null($tutorial)) { $ret = str_replace('abcdef.htm', '', $ret); } return $ret; } /** * Get a URL to a compo.sr page. * * @param array $params URL map * @param ID_TEXT $zone Zone * @return URLPATH URL to page */ function get_brand_page_url($params, $zone) { // Assumes brand site supports .htm URLs, which it should return get_brand_base_url() . (($zone == '') ? '' : '/') . $zone . '/' . urlencode(str_replace('_', '-', $params['page'])) . '.htm'; } /** * Get the brand name. * * @return string The brand name */ function brand_name() { $value = function_exists('get_value') ? get_value('rebrand_name') : null; if ($value === null) { $value = 'Composr'; } return $value; } /** * Find if we're on an Conversr satellite site. * * @return boolean If we are */ function is_cns_satellite_site() { if (get_forum_type() != 'cns') { return false; } return (isset($GLOBALS['FORUM_DB'])) && ((get_db_site() != get_db_forums()) || (get_db_site_host() != get_db_forums_host()) || (get_db_site_user() != get_db_forums_user())); } /** * Convert GUIDs to IDs in some text. * * @param string $text Input text * @return string Output text */ function convert_guids_to_ids($text) { if (!addon_installed('commandr')) { return $text; } $matches = array(); $num_matches = preg_match_all('#^{?([0-9a-fA-F]){8}(-([0-9a-fA-F]){4}){3}-([0-9a-fA-F]){12}}?$#', $text, $matches); if ($num_matches != 0) { require_code('resource_fs'); $guids = array(); for ($i = 0; $i < $num_matches; $i++) { $guids[] = $matches[0][$i]; } $mappings = find_ids_via_guids($guids); foreach ($mappings as $guid => $id) { $text = str_replace($guid, $id, $text); } } return $text; } /** * Set if a mass-import is in progress. * * @param boolean $doing_mass_import If it is */ function set_mass_import_mode($doing_mass_import = true) { global $MASS_IMPORT_HAPPENING; $MASS_IMPORT_HAPPENING = $doing_mass_import; } /** * Find if a mass-import is in progress. * * @return boolean If it is */ function get_mass_import_mode() { global $MASS_IMPORT_HAPPENING; return $MASS_IMPORT_HAPPENING; } /** * Prepare an argument for use literally in a command. Works around common PHP restrictions. * * @param string $arg The argument. * @return string Escaped. */ function escapeshellarg_wrap($arg) { if (php_function_allowed('escapeshellarg')) { return escapeshellarg($arg); } return "'" . addslashes(str_replace(array(chr(0), "'"), array('', "'\"'\"'"), $arg)) . "'"; } /** * Find whether Composr is running on a local network, rather than a live-site. * * @return boolean If it is running locally */ function running_locally() { return (substr(cms_srv('HTTP_HOST'), 0, 8) == '192.168.') || (substr(cms_srv('HTTP_HOST'), 0, 7) == '10.0.0.') || (in_array(cms_srv('HTTP_HOST'), array('localhost'))); } /** * Exit if we are running on a Google App Engine application (live or development). */ function appengine_general_guard() { if (GOOGLE_APPENGINE) { warn_exit(do_lang_tempcode('NOT_ON_GOOGLE_APPENGINE')); } } /** * Exit if we are running on a live Google App Engine application. */ function appengine_live_guard() { if (appengine_is_live()) { warn_exit(do_lang_tempcode('NOT_ON_LIVE_GOOGLE_APPENGINE')); } } /** * Check serialized data for objects, as a security measure. * * @param string $data &$data Serialized data * @param ?mixed $safe_replacement What to substitute if objects are contained (null: substitute null) */ function secure_serialized_data(&$data, $safe_replacement = null) { // Security check, unserialize can result in unchecked magic method invocation on defined objects // Would be a vulnerability if there's a defined class where such method invocation has dangerous side-effects $matches = array(); $num_matches = preg_match_all('#(^|;)O:[\d\+\-\.]+:"([^"]+)"#', $data, $matches); for ($i = 0; $i < $num_matches; $i++) { $harsh = true; // Could be turned into a method parameter later, if needed if ($harsh) { $bad_methods = array( '__.*', 'code_to_preexecute', ); } else { $bad_methods = array( '__sleep', '__wakeup', '__destruct', '__toString', '__set_state', '__isset', '__get', '__set', '__call', '__callStatic', 'code_to_preexecute', ); } $class_name = $matches[2][$i]; $methods = get_class_methods($class_name); foreach ($bad_methods as $bad_method) { foreach ($methods as $method) { if (preg_match('#^' . $bad_method . '$#', $method) != 0) { $data = serialize($safe_replacement); return; } } } } } /** * Creates a PHP value from a stored representation. * Wraps the fact that new versions of PHP have better security, but old ones won't let you pass the extra parameter. * * @param string $data Serialized string. * @return ~mixed What was originally serialised (false: bad data given, or actually false was serialized). */ function cms_unserialize($data) { if (version_compare(PHP_VERSION, '7.0.0') >= 0) { return unserialize($data, array('allowed_classes' => false)); } return unserialize($data); } /** * Update a catalogue content field reference, to a new value. * * @param ID_TEXT $type Content type * @param ID_TEXT $from Old value * @param ID_TEXT $to New value */ function update_catalogue_content_ref($type, $from, $to) { if (strpos(get_db_type(), 'mysql') !== false) { $GLOBALS['SITE_DB']->query_update('catalogue_fields f JOIN ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'catalogue_efv_short v ON v.cf_id=f.id', array('cv_value' => $to), array('cv_value' => $from, 'cf_type' => $type)); } else { $fields = $GLOBALS['SITE_DB']->query_select('catalogue_fields', array('id'), array('cf_type' => $type)); foreach ($fields as $field) { $GLOBALS['SITE_DB']->query_update('catalogue_efv_short', array('cv_value' => $to), array('cv_value' => $from, 'cf_id' => $field['id'])); } } } /** * Start a profiling block, for a specified identifier (of your own choosing). * * @param ID_TEXT $identifier Identifier */ function cms_profile_start_for($identifier) { require_code('profiler'); _cms_profile_start_for($identifier); } /** * End a profiling block, for a specified identifier (of your own choosing - but you must have started it with cms_profile_start_for). * * @param ID_TEXT $identifier Identifier * @param ?string $specifics Longer details of what happened (e.g. a specific SQL query that ran) (null: none provided) */ function cms_profile_end_for($identifier, $specifics = null) { require_code('profiler'); _cms_profile_end_for($identifier, $specifics); } /** * Put out some benign HTTP output. * FastCGI seems to have a weird issue with 'slowish spiky process not continuing with output' - this works around it. Not ideal as would break headers in any subsequent code. */ function send_http_output_ping() { global $DOING_OUTPUT_PINGS; $DOING_OUTPUT_PINGS = true; if (running_script('index')) { if (!headers_sent()) { safe_ini_set('zlib.output_compression', 'Off'); // Otherwise it can compress all the spaces to nothing cms_ob_end_clean(); // Otherwise flushing won't help } echo ' '; flush(); } } /** * Improve security by turning on a strict CSP that only allows stuff from partner sites and disables frames and forms. * Must be called before page output starts. * * @param ?MEMBER $enable_more_open_html_for Allow more open HTML for a particular member ID (null: no member). It still will use the HTML blacklist functionality (unless they have even higher access already), but will remove the more restrictive whitelist functionality. Use of set_high_security_csp here is further decreasing the risk from dangerous HTML, even though the risk should be very low anyway due to the blacklist filter. */ function set_high_security_csp($enable_more_open_html_for = null) { require_code('input_filter'); $_partners = get_allowed_partner_sites(); if ($_partners == array()) { $partners = ''; } else { $partners = ' ' . implode(' ', $_partners); $partners .= ' https://' . implode(' https://', $_partners); $partners .= ' http://' . implode(' http://', $_partners); } $value = ""; $value .= "script-src 'self'{$partners}; "; // browser will check mime-type, so okay for self $value .= "style-src 'self'{$partners}; "; // browser will check mime-type, so okay for self $value .= "object-src 'none'; "; // browser may not check mime-type, so none $value .= "frame-src 'none'; child-src 'none'; "; $value .= "form-action 'self'; "; $value .= "base-uri 'self'; "; $value .= "frame-ancestors 'self'{$partners}; "; header('Content-Security-Policy:' . trim($value)); if ($enable_more_open_html_for !== null) { global $PRIVILEGE_CACHE; has_privilege($enable_more_open_html_for, 'allow_html'); // Force loading, so we can amend the cached value cleanly $PRIVILEGE_CACHE[$enable_more_open_html_for]['allow_html'][''][''][''] = 1; } } /** * Set a CSP header to not allow any frames to include us. */ function set_no_clickjacking_csp() { require_code('input_filter'); $_partners = get_allowed_partner_sites(); if ($_partners == array()) { $partners = ''; } else { $partners = ' ' . implode(' ', $_partners); $partners .= ' https://' . implode(' https://', $_partners); $partners .= ' http://' . implode(' http://', $_partners); } $value = ""; $value .= "frame-ancestors 'self'{$partners}; "; @header('Content-Security-Policy:' . trim($value)); } /** * Stop the web browser trying to save us, and breaking some requests in the process. */ function disable_browser_xss_detection() { @header('X-XSS-Protection: 0'); } /** * Whether smart decaching is enabled. It is slightly inefficient but makes site development easier for people. * * @param boolean $support_temporary_disable Support it being temporarily disabled * @return boolean If smart decaching is enabled */ function support_smart_decaching($support_temporary_disable = false) { if ($support_temporary_disable) { global $DISABLE_SMART_DECACHING_TEMPORARILY; if ($DISABLE_SMART_DECACHING_TEMPORARILY) { return false; } } static $has_in_url = null; if ($has_in_url === null) { $has_in_url = (get_param_integer('keep_smart_decaching', 0) == 1); } if ($has_in_url) { return true; } global $SITE_INFO; if (!empty($SITE_INFO['disable_smart_decaching'])) { if ($SITE_INFO['disable_smart_decaching'] == '1') { return false; } static $has_temporary = null; if ($has_temporary === null) { $has_temporary = false; $matches = array(); if (preg_match('#^(\d+):(.*)$#', $SITE_INFO['disable_smart_decaching'], $matches) != 0) { $time = intval($matches[1]); $path = $matches[2]; if (is_file($path) && filemtime($path) > time() - $time) { $has_temporary = true; } } } return $has_temporary; } return true; // By default it is on } /** * For performance reasons disable smart decaching for cases that allow it to be disabled temporarily (it does a lot of file system checks). */ function disable_smart_decaching_temporarily() { global $DISABLE_SMART_DECACHING_TEMPORARILY; $DISABLE_SMART_DECACHING_TEMPORARILY = true; } /** * Find if the current request has POST fields worth considering/propagating. Very standard framework fields will be ignored. * * @return boolean Whether it does */ function has_interesting_post_fields() { $post = $_POST; $to_ignore = array( 'csrf_token', 'y' . md5(get_site_name() . ': antispam'), 'login_username', 'password', 'remember_me', 'login_invisible', 'redirect', 'redirect_passon', ); foreach ($to_ignore as $field) { unset($post[$field]); } return (count($post) !== 0); } /** * Apply escaping for an HTTP header. * * @param string $str Text to insert into header * @param boolean $within_quotes Text is between quotes * @return string Escaped text */ function escape_header($str, $within_quotes = false) { if ($within_quotes) { $str = addslashes($str); } return str_replace(array("\r", "\n"), array('', ''), $str); } /** * Find if a forum post is a spacer post. * * @param string $post The spacer post * @return array A pair: Whether it is, and the language it is in */ function is_spacer_post($post) { if (substr($post, 0, 10) == '[semihtml]') { $post = substr($post, 10); } $langs = find_all_langs(); foreach (array_keys($langs) as $lang) { $matcher = do_lang('SPACER_POST_MATCHER', null, null, null, $lang); if (substr($post, 0, strlen($matcher)) == $matcher) { return array(true, $lang); } } return array(false, get_site_default_lang()); } /** * Get the Internet host name corresponding to a given IP address. * * @param string $ip_address IP address * @return string Host name OR IP address if failed to look up */ function cms_gethostbyaddr($ip_address) { $hostname = ''; if ((php_function_allowed('shell_exec')) && (function_exists('get_value')) && (get_value('slow_php_dns') === '1')) { $hostname = trim(preg_replace('#^.* #', '', shell_exec('host ' . escapeshellarg_wrap($ip_address)))); } if ($hostname == '') { if (php_function_allowed('gethostbyaddr')) { $hostname = @gethostbyaddr($ip_address); } } if ($hostname == '') { $hostname = $ip_address; } return $hostname; } /** * Get the IP address corresponding to a given Internet host name. * * @param string $hostname Host name * @return string IP address OR host name if failed to look up */ function cms_gethostbyname($hostname) { $ip_address = ''; if ((php_function_allowed('shell_exec')) && (function_exists('get_value')) && (get_value('slow_php_dns') === '1')) { $ip_address = preg_replace('#^.*has address (\d+\.\d+\.\d+).*#s', '$1', shell_exec('host ' . escapeshellarg_wrap($hostname))); } if ($ip_address == '') { if (php_function_allowed('gethostbyaddr')) { $ip_address = @gethostbyaddr($ip_address); } } if ($ip_address == '') { $ip_address = $hostname; } return $ip_address; } /** * Unpack some bytes to an integer, so we can do some bitwise arithmetic on them. * Assumes unsigned, unless you request 4 bytes. * * @param string $str Input string * @param ?integer $bytes How many bytes to read (null: as many as there are in $str) * @set 1 2 4 * @param boolean $little_endian Whether to use little endian (Intel order) as opposed to big endian (network/natural order) * @return integer Read integer */ function cms_unpack_to_uinteger($str, $bytes = null, $little_endian = false) { if ($bytes === null) { $bytes = strlen($str); } switch ($bytes) { case 1: $result = unpack('C', $str); break; case 2: $result = unpack($little_endian ? 'v' : 'n', $str); break; case 4: $result = unpack($little_endian ? 'V' : 'N', $str); break; default: warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } return $result[1]; } /** * Perform a regular expression match. * Automatically applies utf-8 if possible and appropriate. \s is not actually Unicode-safe, for example (as it matches non-breaking-spaces). * * @param string $pattern The pattern. * @param string $subject The subject string. * @param ?array $matches Where matches will be put (note that it is a list of maps, except the arrays are turned inside out) (null: do not store matches). Note that this is actually passed by reference, but is also optional. (null: don't gather) * @param integer $flags Either 0, or PREG_OFFSET_CAPTURE. * @param integer $offset Offset to start from. Usually use with 'A' modifier to anchor it (using '^' in the pattern will not work) * @return ~integer The number of matches (false: error). */ function cms_preg_match_safe($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) { if (get_charset() == 'utf-8') { $result = @preg_match($pattern . 'u', $subject, $matches, $flags, $offset); if ($result !== false) { return $result; } } return preg_match($pattern, $subject, $matches, $flags, $offset); } /** * Array entries that match the pattern. * Automatically applies utf-8 if possible and appropriate. \s is not actually Unicode-safe, for example (as it matches non-breaking-spaces). * * @param string $pattern The pattern. * @param array $subject The subject strings. * @param integer $flags Either 0, or PREG_GREP_INVERT. * @return array Matches. */ function cms_preg_grep_safe($pattern, $subject, $flags = 0) { if (get_charset() == 'utf-8') { $result = @preg_grep($pattern . 'u', $subject, $flags); if ($result !== false) { return $result; } } return preg_grep($pattern, $subject, $flags); } /** * Perform a global regular expression match. * Automatically applies utf-8 if possible and appropriate. \s is not actually Unicode-safe, for example (as it matches non-breaking-spaces). * * @param string $pattern The pattern. * @param string $subject The subject string. * @param ?array $matches Where matches will be put (note that it is a list of maps, except the arrays are turned inside out). Note that this is actually passed by reference, but is also optional. (null: don't gather) * @param integer $flags Either 0, or PREG_OFFSET_CAPTURE. * @return ~integer The number of matches (false: error). */ function cms_preg_match_all_safe($pattern, $subject, &$matches, $flags = 0) { if (get_charset() == 'utf-8') { $result = @preg_match_all($pattern . 'u', $subject, $matches, $flags); if ($result !== false) { return $result; } } return preg_match_all($pattern, $subject, $matches, $flags); } /** * Perform a regular expression search and replace. * Automatically applies utf-8 if possible and appropriate. \s is not actually Unicode-safe, for example (as it matches non-breaking-spaces). * * @param mixed $pattern The pattern (string or array). * @param mixed $replacement The replacement string (string or array). * @param string $subject The subject string. * @param integer $limit The limit of replacements (-1: no limit). * @return ~string The string with replacements made (false: error). */ function cms_preg_replace_safe($pattern, $replacement, $subject, $limit = -1) { if (get_charset() == 'utf-8') { $result = @preg_replace($pattern . 'u', $replacement, $subject, $limit); if ($result !== false) { return $result; } } return preg_replace($pattern, $replacement, $subject, $limit); } /** * Perform a regular expression search and replace using a callback. * Automatically applies utf-8 if possible and appropriate. \s is not actually Unicode-safe, for example (as it matches non-breaking-spaces). * * @param string $pattern The pattern. * @param mixed $callback The callback. * @param string $subject The subject string. * @param integer $limit The limit of replacements (-1: no limit). * @return ~string The string with replacements made (false: error). */ function cms_preg_replace_callback_safe($pattern, $callback, $subject, $limit = -1) { if (get_charset() == 'utf-8') { $result = @preg_replace_callback($pattern . 'u', $callback, $subject, $limit); if ($result !== false) { return $result; } } return preg_replace_callback($pattern, $callback, $subject, $limit); } /** * Split string by a regular expression. * Automatically applies utf-8 if possible and appropriate. \s is not actually Unicode-safe, for example (as it matches non-breaking-spaces). * * @param string $pattern The pattern. * @param string $subject The subject. * @param ?integer $max_splits The maximum number of splits to make (null: no limit). * @param ?integer $mode The special mode (null: none). * @return array The array due to splitting. */ function cms_preg_split_safe($pattern, $subject, $max_splits = null, $mode = null) { if (get_charset() == 'utf-8') { $result = @preg_split($pattern . 'u', $subject, $max_splits, $mode); if ($result !== false) { return $result; } } return preg_split($pattern, $subject, $max_splits, $mode); } | ||||
Time estimation (hours) | |||||
Sponsorship open | |||||
|
Chris - I uploaded the new global3.php to sarhm.org, cleared my cache on the site as well as my browser, resulting in no change. I did file count comparison (only *.php files), between the contents of the sources folder of my upgraded to 10.0.26 and the downloaded(uninstalled) zip of 10.0.26. 272 files on blmiers.com 275 files on sarhm.org 303 files in the zip I tried uploading a new copy of all of the 303 files into blmiers.com, and that did not resolve the problem. I also copied and overwrote /themes, which gave me a whole list of missing images. Fortunately, I did make a complete backup prior to my experiment and was able to restore the site (still getting the same error). |
|
Sorry I only gave you 50% of the fix. A simpler fix is to remove ", array('allowed_classes' => false)" from sources/ajax.php. I just tested this on sarhm.org and it worked. Please don't upload missing files, as you'll be putting back files from addons you've uninstalled, which can cause some major issues due to inconsistency then with the database contents. |
Date Modified | Username | Field | Change |
---|---|---|---|
2019-06-15 15:21 | RailDude64 | New Issue | |
2019-06-15 15:21 | RailDude64 | File Added: composr mass-add to gallery error.PNG | |
2019-06-15 15:21 | RailDude64 | File Added: composr move topic to forum error.PNG | |
2019-06-22 03:58 | Chris Graham | Assigned To | => Chris Graham |
2019-06-22 03:58 | Chris Graham | Status | Not Assigned => Resolved |
2019-06-22 03:58 | Chris Graham | Resolution | open => fixed |
2019-06-22 03:59 | Chris Graham | File Added: global3.php | |
2019-06-24 00:52 | RailDude64 | Status | Resolved => Not Assigned |
2019-06-24 00:52 | RailDude64 | Resolution | fixed => reopened |
2019-06-24 00:52 | RailDude64 | Note Added: 0005982 | |
2019-06-26 17:10 | Guest | Note Added: 0005985 | |
2019-06-26 17:10 | Chris Graham | Status | Not Assigned => Resolved |
2019-06-26 17:10 | Chris Graham | Resolution | reopened => fixed |
2023-02-26 18:29 | Chris Graham | Category | General => General / Uncategorised |