Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
b2evolution/inc/_core/_misc.funcs.php /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
10755 lines (9269 sloc)
311 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * This file implements general purpose functions. | |
| * | |
| * This file is part of the evoCore framework - {@link http://evocore.net/} | |
| * See also {@link https://github.com/b2evolution/b2evolution}. | |
| * | |
| * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license} | |
| * | |
| * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/} | |
| * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}. | |
| * Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}. | |
| * | |
| * @package evocore | |
| */ | |
| if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' ); | |
| /** | |
| * Dependencies | |
| */ | |
| load_funcs('antispam/model/_antispam.funcs.php'); | |
| load_funcs('tools/model/_email.funcs.php'); | |
| load_funcs('sessions/model/_cookie.funcs.php'); | |
| // @todo sam2kb> Move core functions get_admin_skins, get_filenames, cleardir_r, rmdir_r and some other | |
| // to a separate file, and split files_Module from _core_Module | |
| load_funcs('files/model/_file.funcs.php'); | |
| // Load utf8 support functions | |
| load_funcs( '_ext/_portable_utf8.php' ); | |
| load_funcs( '_ext/random/random.php' ); | |
| /** | |
| * Call a method for all modules in a row | |
| * | |
| * @param string the name of the method which should be called | |
| * @param array params | |
| * @return array[module_name][return value], or NULL if the method doesn't have any return value | |
| */ | |
| function modules_call_method( $method_name, $params = NULL ) | |
| { | |
| global $modules; | |
| $result = NULL; | |
| foreach( $modules as $module ) | |
| { | |
| $Module = & $GLOBALS[$module.'_Module']; | |
| if( $params == NULL ) | |
| { | |
| $ret = $Module->{$method_name}(); | |
| } | |
| else | |
| { | |
| $ret = $Module->{$method_name}( $params ); | |
| } | |
| if( isset( $ret ) ) | |
| { | |
| $result[$module] = $ret; | |
| } | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Call a method for all modules in a row and update params by reference | |
| * | |
| * @param string the name of the method which should be called | |
| * @param array params | |
| * @return array[module_name][return value], or NULL if the method doesn't have any return value | |
| */ | |
| function modules_call_method_reference_params( $method_name, & $params ) | |
| { | |
| global $modules; | |
| $result = NULL; | |
| foreach( $modules as $module ) | |
| { | |
| $Module = & $GLOBALS[$module.'_Module']; | |
| $ret = $Module->{$method_name}( $params ); | |
| if( isset( $ret ) ) | |
| { | |
| $result[$module] = $ret; | |
| } | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Loads the b2evo database scheme. | |
| * | |
| * This gets updated through {@link db_delta()} which generates the queries needed to get | |
| * to this scheme. | |
| * | |
| * @param boolean set true to load installed plugins table as well, leave it on false otherwise | |
| * - currently used only on table normalization | |
| * | |
| * Please see {@link db_delta()} for things to take care of. | |
| */ | |
| function load_db_schema( $inlcude_plugins = false ) | |
| { | |
| global $schema_queries; | |
| global $modules, $inc_path; | |
| global $db_storage_charset, $DB; | |
| if( empty( $db_storage_charset ) ) | |
| { // If no specific charset has been requested for datstorage, use the one of the current connection (optimize for speed - no conversions) | |
| $db_storage_charset = $DB->get_connection_charset(); | |
| } | |
| // Load modules: | |
| foreach( $modules as $module ) | |
| { | |
| echo get_install_format_text_and_log( 'Loading module: <code>'.$module.'/model/_'.$module.'.install.php</code><br />', 'br' ); | |
| require_once $inc_path.$module.'/model/_'.$module.'.install.php'; | |
| } | |
| if( $inlcude_plugins ) | |
| { // Load all plugins table into the schema queries | |
| global $Plugins; | |
| if( empty( $Plugins ) ) | |
| { | |
| load_class( 'plugins/model/_plugins.class.php', 'Plugins' ); | |
| $Plugins = new Plugins(); | |
| } | |
| $admin_Plugins = & get_Plugins_admin(); | |
| $admin_Plugins->restart(); | |
| while( $loop_Plugin = & $admin_Plugins->get_next() ) | |
| { // loop through all installed plugins | |
| $create_table_queries = $loop_Plugin->GetDbLayout(); | |
| foreach( $create_table_queries as $create_table_query ) | |
| { | |
| if( ! preg_match( '|^\s*CREATE TABLE\s+(IF NOT EXISTS\s+)?([^\s(]+).*$|is', $create_table_query, $match) ) | |
| { // Could not parse the CREATE TABLE command | |
| continue; | |
| } | |
| $schema_queries[$match[2]] = array( 'Creating table for plugin', $create_table_query ); | |
| $DB->dbaliases[] = '#\b'.$match[2].'\b#'; | |
| $DB->dbreplaces[] = $match[2]; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * @deprecated kept only for plugin backward compatibility (core is being modified to call getters directly) | |
| * To be removed, maybe in b2evo v5. | |
| * | |
| * @return DataObjectCache | |
| */ | |
| function & get_Cache( $objectName ) | |
| { | |
| global $Plugins; | |
| global $$objectName; | |
| if( isset( $$objectName ) ) | |
| { // Cache already exists: | |
| return $$objectName; | |
| } | |
| $func_name = 'get_'.$objectName; | |
| if( function_exists($func_name) ) | |
| { | |
| return $func_name(); | |
| } | |
| else | |
| { | |
| debug_die( 'getCache(): Unknown Cache type get function:'.$func_name.'()' ); | |
| } | |
| } | |
| /** | |
| * Load functions file | |
| */ | |
| function load_funcs( $funcs_path ) | |
| { | |
| global $inc_path; | |
| require_once $inc_path.$funcs_path; | |
| } | |
| /** | |
| * Shutdown function: save HIT and update session! | |
| * | |
| * This is registered in _main.inc.php with register_shutdown_function() | |
| * This is called by PHP at the end of the script. | |
| * | |
| * NOTE: before PHP 4.1 nothing can be echoed here any more, but the minimum PHP requirement for b2evo is PHP 4.3 | |
| */ | |
| function shutdown() | |
| { | |
| /** | |
| * @var Hit | |
| */ | |
| global $Hit; | |
| /** | |
| * @var Session | |
| */ | |
| global $Session; | |
| global $Settings, $Debuglog, $DB, $Timer; | |
| // Try forking a background process and let the parent return as fast as possbile. | |
| if( is_callable('pcntl_fork') && function_exists('posix_kill') && defined('STDIN') ) | |
| { | |
| if( $pid = pcntl_fork() ) | |
| return; // Parent | |
| function shutdown_kill() | |
| { | |
| posix_kill(posix_getpid(), SIGHUP); | |
| } | |
| if ( ob_get_level() ) | |
| { // Discard the output buffer and close | |
| ob_end_clean(); | |
| } | |
| fclose(STDIN); // Close all of the standard | |
| fclose(STDOUT); // file descriptors as we | |
| fclose(STDERR); // are running as a daemon. | |
| register_shutdown_function('shutdown_kill'); | |
| if( posix_setsid() < 0 ) | |
| return; | |
| if( $pid = pcntl_fork() ) | |
| return; // Parent | |
| // Now running as a daemon. This process will even survive | |
| // an apachectl stop. | |
| } | |
| $Timer->resume('shutdown'); | |
| // echo '*** SHUTDOWN FUNC KICKING IN ***'; | |
| // fp> do we need special processing if we are in CLI mode? probably earlier actually | |
| // if( ! $is_cli ) | |
| // Note: it might be useful at some point to do special processing if the script has been aborted or has timed out | |
| // connection_aborted() | |
| // connection_status() | |
| // Save the current HIT, but set delayed since the hit ID will not be required here: | |
| $Hit->log( true ); | |
| // Update the SESSION: | |
| $Session->dbsave(); | |
| // Get updates here instead of slowing down normal display of the dashboard | |
| load_funcs( 'dashboard/model/_dashboard.funcs.php' ); | |
| b2evonet_get_updates(); | |
| // Auto pruning of old HITS, old SESSIONS and potentially MORE analytics data: | |
| if( $Settings->get( 'auto_prune_stats_mode' ) == 'page' ) | |
| { // Autopruning is requested | |
| load_class( 'sessions/model/_hitlist.class.php', 'Hitlist' ); | |
| Hitlist::dbprune(); // will prune once per day, according to Settings | |
| } | |
| // Calling debug_info() here will produce complete data but it will be after </html> hence invalid. | |
| // Then again, it's for debug only, so it shouldn't matter that much. | |
| debug_info(); | |
| // Update the SESSION again, at the very end: | |
| // (e.g. "Debuglogs" may have been removed in debug_info()) | |
| $Session->dbsave(); | |
| while( $DB->transaction_nesting_level ) | |
| { // Rollback all transactions which could not finished correctly by some unknown error, | |
| // Used to avoid errors like "Lock wait timeout exceeded; try restarting transaction": | |
| $DB->rollback(); | |
| } | |
| $Timer->pause('shutdown'); | |
| } | |
| /***** Formatting functions *****/ | |
| /** | |
| * Format a string/content for being output | |
| * | |
| * @author fplanque | |
| * @todo htmlspecialchars() takes a charset argument, which we could provide ($evo_charset?) | |
| * @param string raw text | |
| * @param string format, can be one of the following | |
| * - raw: do nothing | |
| * - htmlbody: display in HTML page body: allow full HTML | |
| * - entityencoded: Special mode for RSS 0.92: allow full HTML but escape it | |
| * - htmlhead: strips out HTML (mainly for use in Title) | |
| * - htmlattr: use as an attribute: escapes quotes, strip tags | |
| * - formvalue: use as a form value: escapes quotes and < > but leaves code alone | |
| * - text: use as plain-text, e.g. for ascii-mails | |
| * - xml: use in an XML file: strip HTML tags | |
| * - xmlattr: use as an attribute: strips tags and escapes quotes | |
| * @return string formatted text | |
| */ | |
| function format_to_output( $content, $format = 'htmlbody' ) | |
| { | |
| global $Plugins, $evo_charset; | |
| switch( $format ) | |
| { | |
| case 'raw': | |
| // do nothing! | |
| break; | |
| case 'htmlbody': | |
| // display in HTML page body: allow full HTML | |
| $content = convert_chars($content, 'html'); | |
| break; | |
| case 'urlencoded': | |
| // Encode string to be passed as part of an URL | |
| $content = rawurlencode( $content ); | |
| break; | |
| case 'entityencoded': | |
| // Special mode for RSS 0.92: apply renders and allow full HTML but escape it | |
| $content = convert_chars($content, 'html'); | |
| $content = htmlspecialchars( $content, ENT_QUOTES, $evo_charset ); | |
| break; | |
| case 'htmlfeed': | |
| // For use in RSS <content:encoded><![CDATA[ ... ]]></content:encoded> | |
| // allow full HTML + absolute URLs... | |
| $content = make_rel_links_abs($content); | |
| $content = convert_chars($content, 'html'); | |
| $content = str_replace(']]>', ']]>', $content); // encode CDATA closing tag to prevent injection/breaking of the <![CDATA[ ... ]]> | |
| break; | |
| case 'htmlhead': | |
| // Strips out HTML (mainly for use in Title) | |
| $content = strip_tags($content); | |
| $content = convert_chars($content, 'html'); | |
| break; | |
| case 'htmlattr': | |
| // use as an attribute: strips tags and escapes quotes | |
| // TODO: dh> why not just htmlspecialchars?fp> because an attribute can never contain a tag? dh> well, "onclick='return 1<2;'" would get stripped, too. I'm just saying: why mess with it, when we can just use htmlspecialchars.. fp>ok | |
| $content = strip_tags($content); | |
| $content = convert_chars($content, 'html'); | |
| $content = str_replace( array('"', "'"), array('"', '''), $content ); | |
| break; | |
| case 'htmlspecialchars': | |
| case 'formvalue': | |
| // Replace special chars to &, ", '|', < and > : | |
| // Handles & " ' < > to & " ' < > | |
| $content = htmlspecialchars( $content, ENT_QUOTES | ENT_HTML5, $evo_charset ); | |
| break; | |
| case 'xml': | |
| // use in an XML file: strip HTML tags | |
| $content = strip_tags($content); | |
| $content = convert_chars($content, 'xml'); | |
| break; | |
| case 'xmlattr': | |
| // use as an attribute: strips tags and escapes quotes | |
| $content = strip_tags($content); | |
| $content = convert_chars($content, 'xml'); | |
| $content = str_replace( array('"', "'"), array('"', '''), $content ); | |
| break; | |
| case 'text': | |
| // use as plain-text, e.g. for ascii-mails | |
| $content = strip_tags( $content ); | |
| $trans_tbl = get_html_translation_table( HTML_ENTITIES ); | |
| $trans_tbl = array_flip( $trans_tbl ); | |
| $content = strtr( $content, $trans_tbl ); | |
| $content = preg_replace( '/[ \t]+/', ' ', $content); | |
| $content = trim($content); | |
| break; | |
| case 'syslog': | |
| // Replace special chars to &, ", '|', < and > : | |
| // Handles & " ' < > to & " ' < > | |
| $content = htmlspecialchars( $content, ENT_QUOTES | ENT_HTML5, $evo_charset ); | |
| $content = preg_replace( "/\[\[(.+?)]]/is", "<code>$1</code>", $content ); // Replaces [[...]] into <code>...</code> | |
| break; | |
| default: | |
| debug_die( 'Output format ['.$format.'] not supported.' ); | |
| } | |
| return $content; | |
| } | |
| /* | |
| * autobrize(-) | |
| */ | |
| function autobrize($content) { | |
| $content = callback_on_non_matching_blocks( $content, '~<code>.+?</code>~is', 'autobrize_callback' ); | |
| return $content; | |
| } | |
| /** | |
| * Adds <br>'s to non code blocks | |
| * | |
| * @param string $content | |
| * @return string content with <br>'s added | |
| */ | |
| function autobrize_callback( $content ) | |
| { | |
| $content = preg_replace("/<br>\n/", "\n", $content); | |
| $content = preg_replace("/<br \/>\n/", "\n", $content); | |
| $content = preg_replace("/(\015\012)|(\015)|(\012)/", "<br />\n", $content); | |
| return($content); | |
| } | |
| /* | |
| * unautobrize(-) | |
| */ | |
| function unautobrize($content) | |
| { | |
| $content = callback_on_non_matching_blocks( $content, '~<code>.+?</code>~is', 'unautobrize_callback' ); | |
| return $content; | |
| } | |
| /** | |
| * Removes <br>'s from non code blocks | |
| * | |
| * @param string $content | |
| * @return string content with <br>'s removed | |
| */ | |
| function unautobrize_callback( $content ) | |
| { | |
| $content = preg_replace("/<br>\n/", "\n", $content); //for PHP versions before 4.0.5 | |
| $content = preg_replace("/<br \/>\n/", "\n", $content); | |
| return($content); | |
| } | |
| /** | |
| * Add leading zeroes to a number when necessary. | |
| * | |
| * @param string The original number. | |
| * @param integer How many digits shall the number have? | |
| * @return string The padded number. | |
| */ | |
| function zeroise( $number, $threshold ) | |
| { | |
| return str_pad( $number, $threshold, '0', STR_PAD_LEFT ); | |
| } | |
| /** | |
| * Get a limited text-only excerpt | |
| * | |
| * @param string | |
| * @param int Maximum length | |
| * @return string | |
| */ | |
| function excerpt( $str, $maxlen = 254, $tail = '…' ) | |
| { | |
| // Add spaces | |
| $str = str_replace( array( '<p>', '<br', '</tr><tr', '</th><th', '</td><td' ), array( ' <p>', ' <br', '</tr> <tr', '</th> <th', '</td> <td' ), $str ); | |
| // Remove <code> | |
| $str = preg_replace( '#<code>(.+)</code>#is', '', $str ); | |
| // Strip tags: | |
| $str = strip_tags( $str ); | |
| // Remove spaces: | |
| $str = preg_replace( '/[ \t]+/', ' ', $str); | |
| $str = trim( $str ); | |
| // Ger rid of all new lines and Display the html tags as source text: | |
| $str = trim( preg_replace( '#[\r\n\t\s]+#', ' ', $str ) ); | |
| $str = strmaxlen( $str, $maxlen, $tail, 'raw', true ); | |
| return $str; | |
| } | |
| /** | |
| * Get a limited text-only excerpt based on number of words | |
| * | |
| * @param string | |
| * @param integer Maximum length | |
| * @param array Params | |
| * @return string | |
| */ | |
| function excerpt_words( $str, $maxwords = 50, $params = array() ) | |
| { | |
| // Add spaces | |
| $str = str_replace( array( '<p>', '<br' ), array( ' <p>', ' <br' ), $str ); | |
| // Remove <code> | |
| $str = preg_replace( '#<code>(.+)</code>#is', '', $str ); | |
| // Strip tags: | |
| $str = strip_tags( $str ); | |
| // Remove spaces: | |
| $str = preg_replace( '/[ \t]+/', ' ', $str); | |
| $str = trim( $str ); | |
| // Ger rid of all new lines and Display the html tags as source text: | |
| $str = trim( preg_replace( '#[\r\n\t\s]+#', ' ', $str ) ); | |
| $str = strmaxwords( $str, $maxwords, $params ); | |
| return $str; | |
| } | |
| /** | |
| * Crop string to maxlen with … (default tail) at the end if needed. | |
| * | |
| * If $format is not "raw", we make sure to not cut in the middle of an | |
| * HTML entity, so that strmaxlen('1&2', 3, NULL, 'formvalue') will not | |
| * become/stay '1&…'. | |
| * | |
| * @param string | |
| * @param int Maximum length | |
| * @param string Tail to use, when string gets cropped. Its length gets | |
| * substracted from the total length (with HTML entities | |
| * being decoded). Default is "…" (HTML entity) | |
| * @param string Format, see {@link format_to_output()} | |
| * @param boolean Crop at whitespace, if possible? | |
| * (any word split at the end will get its head removed) | |
| * @return string | |
| */ | |
| function strmaxlen( $str, $maxlen = 50, $tail = NULL, $format = 'raw', $cut_at_whitespace = false ) | |
| { | |
| if( is_null($tail) ) | |
| { | |
| $tail = '…'; | |
| } | |
| $str = utf8_rtrim($str); | |
| if( utf8_strlen( $str ) > $maxlen ) | |
| { | |
| // Replace all HTML entities by a single char. html_entity_decode for example | |
| // would not handle …. | |
| $tail_for_length = preg_replace('~&\w+?;~', '.', $tail); | |
| $tail_length = utf8_strlen( html_entity_decode($tail_for_length) ); | |
| $len = $maxlen-$tail_length; | |
| if( $len < 1 ) | |
| { // special case; $tail length is >= $maxlen | |
| $len = 0; | |
| } | |
| $str_cropped = utf8_substr( $str, 0, $len ); | |
| if( $format != 'raw' ) | |
| { // if the format isn't raw we make sure that we do not cut in the middle of an HTML entity | |
| $maxlen_entity = 7; # "&" is 5, min 3! | |
| $str_inspect = utf8_substr($str_cropped, 1-$maxlen_entity); | |
| $pos_amp = utf8_strpos($str_inspect, '&'); | |
| if( $pos_amp !== false ) | |
| { // there's an ampersand at the end of the cropped string | |
| $look_until = $pos_amp; | |
| $str_cropped_len = utf8_strlen($str_cropped); | |
| if( $str_cropped_len < $maxlen_entity ) | |
| { // we have to look at least for the length of an entity | |
| $look_until += $maxlen_entity-$str_cropped_len; | |
| } | |
| if( strpos(utf8_substr($str, $len, $look_until), ';') !== false ) | |
| { | |
| $str_cropped = utf8_substr( $str, 0, $len-utf8_strlen($str_inspect)+$pos_amp); | |
| } | |
| } | |
| } | |
| if( $cut_at_whitespace ) | |
| { | |
| // Get the first character being cut off. Note: we can't use $str[index] in case of utf8 strings! | |
| $first_cut_off_char = utf8_substr( $str, utf8_strlen( $str_cropped ), 1 ); | |
| if( ! ctype_space( $first_cut_off_char ) ) | |
| { // first character being cut off is not whitespace | |
| // Get the chars as an array from the cropped string to be able to get chars by position | |
| $str_cropped_chars = preg_split('//u',$str_cropped, -1, PREG_SPLIT_NO_EMPTY); | |
| $i = utf8_strlen($str_cropped); | |
| while( $i && isset( $str_cropped_chars[ --$i ] ) && ! ctype_space( $str_cropped_chars[ $i ] ) ) | |
| {} | |
| if( $i ) | |
| { | |
| $str_cropped = utf8_substr($str_cropped, 0, $i); | |
| } | |
| } | |
| } | |
| $str = format_to_output(utf8_rtrim($str_cropped), $format); | |
| $str .= $tail; | |
| return $str; | |
| } | |
| else | |
| { | |
| return format_to_output($str, $format); | |
| } | |
| } | |
| /** | |
| * Crop string to maxwords preserving tags. | |
| * | |
| * @param string | |
| * @param int Maximum number words | |
| * @param mixed array Optional parameters | |
| * @return string | |
| */ | |
| function strmaxwords( $str, $maxwords = 50, $params = array() ) | |
| { | |
| $params = array_merge( array( | |
| 'cutting_mark' => '…', | |
| 'continued_link' => '', | |
| 'continued_text' => '…', | |
| 'continued_class' => '', | |
| 'always_continue' => false, | |
| ), $params ); | |
| // STATE MACHINE: | |
| $in_tag = false; | |
| $have_seen_non_whitespace = false; | |
| $end = strlen( $str ); // If we use utf8_strlen here(), we also need to access UTF chars below | |
| for( $i = 0; $i < $end; $i++ ) | |
| { | |
| switch( $char = $str[$i] ) // This is NOT UTF-8 | |
| { | |
| case '<' : // start of a tag | |
| $in_tag = true; | |
| break; | |
| case '>' : // end of a tag | |
| $in_tag = false; | |
| break; | |
| case ctype_space($char): // This is a whitespace char... | |
| if( ! $in_tag ) | |
| { // it's a word gap: | |
| // Eat any additional whitespace: | |
| while( isset($str[$i+1]) && ctype_space($str[$i+1]) ) | |
| { | |
| $i++; | |
| } | |
| if( isset($str[$i+1]) && $have_seen_non_whitespace ) | |
| { // only decrement words, if there's been at least one non-space char before. | |
| --$maxwords; | |
| } | |
| } | |
| // ignore white space in a tag... | |
| break; | |
| default: | |
| $have_seen_non_whitespace = true; | |
| break; | |
| } | |
| if( $maxwords < 1 ) | |
| { // We have reached the cutting point: | |
| break; | |
| } | |
| } | |
| if( $maxwords < 1 ) | |
| { // Cutting is necessary: | |
| // restrict content to required number of words: | |
| $str = utf8_substr( $str, 0, $i ); | |
| // Add a cutting mark: | |
| $str .= $params['cutting_mark']; | |
| // balance the tags out: | |
| $str = balance_tags( $str ); | |
| // remove empty tags: | |
| $str = preg_replace( '~<([\s]+?)[^>]*?></\1>~is', '', $str ); | |
| } | |
| if( $params['always_continue'] || $maxwords < 1 ) | |
| { // we want a continued text if avoid_end_hellip is not set: | |
| if( ! isset( $params['avoid_end_hellip'] ) ) | |
| { | |
| $str .= ' <a href="'.$params['continued_link'].'" class="'.$params['continued_class'].'">'.$params['continued_text'].'</a>'; | |
| } | |
| } | |
| return $str; | |
| } | |
| /** | |
| * Convert all non ASCII chars (except if UTF-8, GB2312 or CP1251) to &#nnnn; unicode references. | |
| * Also convert entities to &#nnnn; unicode references if output is not HTML (eg XML) | |
| * | |
| * Preserves < > and quotes. | |
| * | |
| * fplanque: simplified | |
| * sakichan: pregs instead of loop | |
| */ | |
| function convert_chars( $content, $flag = 'html' ) | |
| { | |
| global $b2_htmltrans, $evo_charset; | |
| /** | |
| * Translation of invalid Unicode references range to valid range. | |
| * These are Windows CP1252 specific characters. | |
| * They would look weird on non-Windows browsers. | |
| * If you've ever pasted text from MSWord, you'll understand. | |
| * | |
| * You should not have to change this. | |
| */ | |
| static $b2_htmltranswinuni = array( | |
| '€' => '€', // the Euro sign | |
| '‚' => '‚', | |
| 'ƒ' => 'ƒ', | |
| '„' => '„', | |
| '…' => '…', | |
| '†' => '†', | |
| '‡' => '‡', | |
| 'ˆ' => 'ˆ', | |
| '‰' => '‰', | |
| 'Š' => 'Š', | |
| '‹' => '‹', | |
| 'Œ' => 'Œ', | |
| 'Ž' => 'ž', | |
| '‘' => '‘', | |
| '’' => '’', | |
| '“' => '“', | |
| '”' => '”', | |
| '•' => '•', | |
| '–' => '–', | |
| '—' => '—', | |
| '˜' => '˜', | |
| '™' => '™', | |
| 'š' => 'š', | |
| '›' => '›', | |
| 'œ' => 'œ', | |
| 'ž' => 'ž', | |
| 'Ÿ' => 'Ÿ' | |
| ); | |
| // Convert highbyte non ASCII/UTF-8 chars to urefs: | |
| if( ! in_array(strtolower($evo_charset), array( 'utf8', 'utf-8', 'gb2312', 'windows-1251') ) ) | |
| { // This is a single byte charset | |
| // fp> why do we actually bother doing this:? | |
| $content = preg_replace_callback( | |
| '/[\x80-\xff]/', | |
| '_convert_chars_callback', | |
| $content); | |
| } | |
| // Convert Windows CP1252 => Unicode (valid HTML) | |
| // TODO: should this go to input conversions instead (?) | |
| $content = strtr( $content, $b2_htmltranswinuni ); | |
| if( $flag == 'html' ) | |
| { // we can use entities | |
| // Convert & chars that are not used in an entity | |
| $content = preg_replace('/&(?![#A-Za-z0-9]{2,20};)/', '&', $content); | |
| } | |
| else | |
| { // unicode, xml... | |
| // Convert & chars that are not used in an entity | |
| $content = preg_replace('/&(?![#A-Za-z0-9]{2,20};)/', '&', $content); | |
| // Convert HTML entities to urefs: | |
| $content = strtr($content, $b2_htmltrans); | |
| } | |
| return( $content ); | |
| } | |
| /** | |
| * Callback for preg_replace_callback in convert_chars() | |
| */ | |
| function _convert_chars_callback( $matches ) | |
| { | |
| return "&#".ord( $matches[0] ).";"; | |
| } | |
| /** | |
| * Get number of bytes in $string. This works around mbstring.func_overload, if | |
| * activated for strlen/mb_strlen. | |
| * @param string | |
| * @return int | |
| */ | |
| function evo_bytes( $string ) | |
| { | |
| $fo = ini_get('mbstring.func_overload'); | |
| if( $fo && $fo & 2 && function_exists('mb_strlen') ) | |
| { // overloading of strlen is enabled | |
| return mb_strlen( $string, 'ASCII' ); | |
| } | |
| return strlen($string); | |
| } | |
| /** | |
| * mbstring wrapper for strtolower function | |
| * | |
| * @deprecated by {@link utf8_strtolower()} | |
| * | |
| * fp> TODO: instead of those "when used" ifs, it would make more sense to redefine | |
| * mb_strtolower beforehand if it doesn"t exist (it would then just be a fallback | |
| * to the strtolower + a Debuglog->add() ) | |
| * | |
| * @param string | |
| * @return string | |
| */ | |
| function evo_strtolower( $string ) | |
| { | |
| global $current_charset; | |
| if( $current_charset != 'iso-8859-1' && $current_charset != '' && function_exists('mb_strtolower') ) | |
| { | |
| return mb_strtolower( $string, $current_charset ); | |
| } | |
| return strtolower($string); | |
| } | |
| /** | |
| * mbstring wrapper for strlen function | |
| * | |
| * @deprecated by {@link utf8_strlen()} | |
| * | |
| * @param string | |
| * @return string | |
| */ | |
| function evo_strlen( $string ) | |
| { | |
| global $current_charset; | |
| if( $current_charset != 'iso-8859-1' && $current_charset != '' && function_exists('mb_strlen') ) | |
| { | |
| return mb_strlen( $string, $current_charset ); | |
| } | |
| return strlen($string); | |
| } | |
| /** | |
| * mbstring wrapper for strpos function | |
| * | |
| * @deprecated by {@link utf8_strpos()} | |
| * | |
| * @param string | |
| * @param string | |
| * @return int | |
| */ | |
| function evo_strpos( $string , $needle , $offset = null ) | |
| { | |
| global $current_charset; | |
| if( $current_charset != 'iso-8859-1' && $current_charset != '' && function_exists('mb_strpos') ) | |
| { | |
| return mb_strpos( $string, $needle, $offset ,$current_charset ); | |
| } | |
| return strpos( $string , $needle , $offset ); | |
| } | |
| /** | |
| * mbstring wrapper for substr function | |
| * | |
| * @deprecated by {@link utf8_substr()} | |
| * | |
| * @param string | |
| * @param int start position | |
| * @param int string length | |
| * @return string | |
| */ | |
| function evo_substr( $string, $start = 0, $length = '#' ) | |
| { | |
| global $current_charset; | |
| if( ! $length ) | |
| { // make mb_substr and substr behave consistently (mb_substr returns string for length=0) | |
| return ''; | |
| } | |
| if( $length == '#' ) | |
| { | |
| $length = utf8_strlen($string); | |
| } | |
| if( $current_charset != 'iso-8859-1' && $current_charset != '' && function_exists('mb_substr') ) | |
| { | |
| return mb_substr( $string, $start, $length, $current_charset ); | |
| } | |
| return substr( $string, $start, $length ); | |
| } | |
| /** | |
| * Split $text into blocks by using $pattern and call $callback on the non-matching blocks. | |
| * | |
| * The non-matching block's text is the first param to $callback and additionally $params gets passed. | |
| * | |
| * This gets used to make links clickable or replace smilies. | |
| * | |
| * E.g., to replace only in non-HTML tags, call it like: | |
| * <code>callback_on_non_matching_blocks( $text, '~<[^>]*>~s', 'your_callback' );</code> | |
| * | |
| * {@internal This function gets tested in misc.funcs.simpletest.php.}} | |
| * | |
| * @param string Text to handle | |
| * @param string Regular expression pattern that defines blocks to exclude. | |
| * @param callback Function name or object/method array to use as callback. | |
| * Each non-matching block gets passed as first param, additional params may be | |
| * passed with $params. | |
| * @param array Of additional ("static") params to $callback. | |
| * @return string | |
| */ | |
| function callback_on_non_matching_blocks( $text, $pattern, $callback, $params = array() ) | |
| { | |
| global $evo_non_matching_blocks; | |
| if( preg_match_all( $pattern, $text, $matches, PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER ) ) | |
| { // $pattern matches, call the callback method on full text except of matching blocks | |
| // Create an unique string in order to replace all matching blocks temporarily | |
| $unique_replacement = md5( time() + rand() ); | |
| if( ! isset( $evo_non_matching_blocks ) ) | |
| { // Init cache array once: | |
| $evo_non_matching_blocks = array(); | |
| } | |
| // Use level in order to don't mix from other recursive calls: | |
| $level = count( $evo_non_matching_blocks ); | |
| $evo_non_matching_blocks[ $level ] = array(); | |
| foreach( $matches[0] as $l => $l_matching ) | |
| { // Build arrays with a source code of the matching blocks and with temporary replacement | |
| if( substr( $l_matching[0], 1, 1 ) == '[' ) | |
| { // Exception for match out short tags, | |
| // We should not replace a char(usually a space) before short tag like [image:123]: | |
| $l_matching[0] = substr( $l_matching[0], 1 ); | |
| } | |
| $evo_non_matching_blocks[ $level ][ '?'.$l.$unique_replacement.$l.'?' ] = $l_matching[0]; | |
| } | |
| // Replace all matching blocks with temporary text like '?X219a33da9c1b8f4e335bffc015df8c96X?' | |
| // where X is index of match block in array $matches[0] | |
| // It is used to avoid any changes in the matching blocks | |
| $text = str_replace( $evo_non_matching_blocks[ $level ], array_keys( $evo_non_matching_blocks[ $level ] ), $text ); | |
| // Callback: | |
| $callback_params = $params; | |
| array_unshift( $callback_params, $text ); | |
| $text = call_user_func_array( $callback, $callback_params ); | |
| // Revert a source code of the matching blocks in content | |
| $text = str_replace( array_keys( $evo_non_matching_blocks[ $level ] ), $evo_non_matching_blocks[ $level ], $text ); | |
| return $text; | |
| } | |
| $callback_params = $params; | |
| array_unshift( $callback_params, $text ); | |
| return call_user_func_array( $callback, $callback_params ); | |
| } | |
| /** | |
| * Replace non matching blocks from temp strings like '?X219a33da9c1b8f4e335bffc015df8c96X?' back to original string like '<code>$some_var = "some value";</code>' | |
| * | |
| * @param array Matches | |
| * @return array Matches | |
| */ | |
| function fix_non_matching_blocks( $matches ) | |
| { | |
| global $evo_non_matching_blocks; | |
| if( ! is_array( $matches ) ) | |
| { // This function works only with array of matches | |
| return $matches; | |
| } | |
| if( ! isset( $evo_non_matching_blocks ) || | |
| ! is_array( $evo_non_matching_blocks ) ) | |
| { // Nothing to fix, Return source param value: | |
| return $matches; | |
| } | |
| $level = count( $evo_non_matching_blocks ) - 1; | |
| if( empty( $evo_non_matching_blocks[ $level ] ) ) | |
| { // Nothing to fix, Return source param value: | |
| return $matches; | |
| } | |
| foreach( $matches as $m => $match ) | |
| { | |
| if( is_array( $match ) ) | |
| { // Fix recursively: | |
| foreach( $match as $s => $sub_match ) | |
| { | |
| $matches[ $m ][ $s ] = str_replace( array_keys( $evo_non_matching_blocks[ $level ] ), $evo_non_matching_blocks[ $level ], $sub_match ); | |
| } | |
| } | |
| else | |
| { // Revert back to original values from temp strings: | |
| $matches[ $m ] = str_replace( array_keys( $evo_non_matching_blocks[ $level ] ), $evo_non_matching_blocks[ $level ], $match ); | |
| } | |
| } | |
| return $matches; | |
| } | |
| /** | |
| * Perform a global regular expression match outside of blocks <code></code>, <pre></pre>, markdown codeblocks `` | |
| * | |
| * @param string Pattern to search for | |
| * @param string Content | |
| * @param array Array of all matches in multi-dimensional array | |
| * @return integer|boolean Number of full pattern matches (which might be zero), or FALSE if an error occurred. | |
| */ | |
| function preg_match_outcode( $search, $content, & $matches ) | |
| { | |
| if( stristr( $content, '<code' ) !== false || stristr( $content, '<pre' ) !== false || strstr( $content, '`' ) !== false ) | |
| { // Call preg_match_all() on everything outside code/pre and markdown codeblocks: | |
| $result = callback_on_non_matching_blocks( $content, | |
| '~(`|<(code|pre)[^>]*>).*?(\1|</\2>)~is', | |
| 'preg_match_outcode_callback', array( $search, & $matches ) ); | |
| // Revert codeblocks back if they are located inside search regexp: | |
| $matches = fix_non_matching_blocks( $matches ); | |
| return $result; | |
| } | |
| else | |
| { // No code/pre blocks, search in the whole thing: | |
| return preg_match_all( $search, $content, $matches ); | |
| } | |
| } | |
| /** | |
| * Used for function callback_on_non_matching_blocks(), because there is different order of params | |
| * | |
| * @param string Pattern to search for | |
| * @param string Content | |
| * @param array Array of all matches in multi-dimensional array | |
| * @return integer|boolean Number of full pattern matches (which might be zero), or FALSE if an error occurred. | |
| */ | |
| function preg_match_outcode_callback( $content, $search, & $matches ) | |
| { | |
| return preg_match_all( $search, $content, $matches ); | |
| } | |
| /** | |
| * Replace content outside of blocks <code></code>, <pre></pre>, markdown codeblocks `` | |
| * | |
| * @deprecated Use new function replace_outside_code_tags() | |
| * | |
| * @param array|string Search list | |
| * @param array|string Replace list or Callback function | |
| * @param string Source content | |
| * @param string Callback function name | |
| * @param string Type of callback function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() (@see replace_content()) | |
| * @return string Replaced content | |
| */ | |
| function replace_content_outcode( $search, $replace, $content, $replace_function_callback = 'replace_content', $replace_function_type = 'preg' ) | |
| { | |
| return replace_outside_code_tags( $search, $replace, $content, $replace_function_callback, $replace_function_type ); | |
| } | |
| /** | |
| * Replace content outside of blocks <code></code>, <pre></pre>, markdown codeblocks `` | |
| * | |
| * @param array|string Search list | |
| * @param array|string Replace list or Callback function | |
| * @param string Source content | |
| * @param string Callback function name | |
| * @param string Type of callback function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() (@see replace_content()) | |
| * @return string Replaced content | |
| */ | |
| function replace_outside_code_tags( $search, $replace, $content, $replace_function_callback = 'replace_content', $replace_function_type = 'preg' ) | |
| { | |
| if( ! empty( $search ) ) | |
| { | |
| if( stristr( $content, '<code' ) !== false || | |
| stristr( $content, '<pre' ) !== false || | |
| strstr( $content, '`' ) !== false ) | |
| { // Call replace_content() on everything outside code/pre, and markdown codeblocks: | |
| $content = callback_on_non_matching_blocks( $content, | |
| '~(`.*?`|' | |
| .'<code[^>]*>.*?</code>|' | |
| .'<pre[^>]*>.*?</pre>)~is', | |
| $replace_function_callback, array( $search, $replace, $replace_function_type ) ); | |
| } | |
| else | |
| { // No code/pre blocks, replace on the whole thing | |
| $content = call_user_func( $replace_function_callback, $content, $search, $replace, $replace_function_type ); | |
| } | |
| } | |
| return $content; | |
| } | |
| /** | |
| * Replace content outside of blocks <code></code>, <pre></pre>, markdown codeblocks `` AND also outside of short tags like [image:123] | |
| * | |
| * @deprecated Use new function replace_outside_code_and_short_tags() | |
| * | |
| * @param array|string Search list | |
| * @param array|string Replace list or Callback function | |
| * @param string Source content | |
| * @param string Callback function name | |
| * @param string Type of callback function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() (@see replace_content()) | |
| * @return string Replaced content | |
| */ | |
| function replace_content_outcode_shorttags( $search, $replace, $content, $replace_function_callback = 'replace_content', $replace_function_type = 'preg' ) | |
| { | |
| return replace_outside_code_and_short_tags( $search, $replace, $content, $replace_function_callback, $replace_function_type ); | |
| } | |
| /** | |
| * Replace content outside of blocks <code></code>, <pre></pre>, markdown codeblocks `` AND also outside of short tags like [image:123] | |
| * | |
| * @param array|string Search list | |
| * @param array|string Replace list or Callback function | |
| * @param string Source content | |
| * @param string Callback function name | |
| * @param string Type of callback function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() (@see replace_content()) | |
| * @return string Replaced content | |
| */ | |
| function replace_outside_code_and_short_tags( $search, $replace, $content, $replace_function_callback = 'replace_content', $replace_function_type = 'preg' ) | |
| { | |
| if( ! empty( $search ) ) | |
| { | |
| if( stristr( $content, '<code' ) !== false || | |
| stristr( $content, '<pre' ) !== false || | |
| strstr( $content, '`' ) !== false || | |
| preg_match( '/\[[a-z]+:[^\]`]+\]/i', $content ) ) | |
| { // Call replace_content() on everything outside code/pre, markdown codeblocks and short tags: | |
| $content = callback_on_non_matching_blocks( $content, | |
| '~(`.*?`|' | |
| .'<code[^>]*>.*?</code>|' | |
| .'<pre[^>]*>.*?</pre>|' | |
| .'[^\[]\[[a-z]+:[^\]`]+\])~is', | |
| $replace_function_callback, array( $search, $replace, $replace_function_type ) ); | |
| } | |
| else | |
| { // No code/pre blocks, replace on the whole thing | |
| $content = call_user_func( $replace_function_callback, $content, $search, $replace, $replace_function_type ); | |
| } | |
| } | |
| return $content; | |
| } | |
| /** | |
| * Replace content, Used for function callback_on_non_matching_blocks(), because there is different order of params | |
| * | |
| * @param string Source content | |
| * @param array|string Search list | |
| * @param array|string Replace list | |
| * @param string Type of function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() | |
| * @param string The maximum possible replacements for each pattern in each subject string. Defaults to -1 (no limit). | |
| * @return string Replaced content | |
| */ | |
| function replace_content( $content, $search, $replace, $type = 'preg', $limit = -1 ) | |
| { | |
| if( $limit == 0 ) | |
| { // Strange request to nothing replace, Return original content: | |
| return $content; | |
| } | |
| switch( $type ) | |
| { | |
| case 'str': | |
| if( $limit == -1 ) | |
| { // Unlimited replace: | |
| return str_replace( $search, $replace, $content ); | |
| } | |
| else | |
| { // Limited replace: | |
| $pos = strpos( $content, $search ); | |
| if( $pos !== false ) | |
| { // Do the limited replacements: | |
| for( $p = 0; $p < $limit; $p++ ) | |
| { | |
| if( $pos === false ) | |
| { // Stop searching: | |
| break; | |
| } | |
| $content = substr_replace( $content, $replace, $pos, strlen( $search ) ); | |
| // Go to next searched substring: | |
| $pos = strpos( $content, $search, $pos + strlen( $replace ) ); | |
| } | |
| } | |
| return $content; | |
| } | |
| case 'preg_callback': | |
| return preg_replace_callback( $search, $replace, $content, $limit ); | |
| default: // 'preg' | |
| return preg_replace( $search, $replace, $content, $limit ); | |
| } | |
| } | |
| /** | |
| * Replace content by callback, Used for function callback_on_non_matching_blocks(), because there is different order of params | |
| * | |
| * @param string Source content | |
| * @param array|string Search list | |
| * @param array|string Replace callback | |
| * @return string Replaced content | |
| */ | |
| function replace_content_callback( $content, $search, $replace_callback ) | |
| { | |
| global $evo_replace_outside_code_tags_callback; | |
| // Store here the requested callback function in order to use pre-processor function fix_replace_content_callback() before we call the original requested callback function: | |
| $evo_replace_outside_code_tags_callback = $replace_callback; | |
| return preg_replace_callback( $search, 'fix_replace_content_callback', $content ); | |
| } | |
| /** | |
| * Replace non matching blocks from temp strings like '?X219a33da9c1b8f4e335bffc015df8c96X?' back to original string like '<code>$some_var = "some value";</code>' | |
| * | |
| * @param Array Matches | |
| * @return mixed | |
| */ | |
| function fix_replace_content_callback( $m ) | |
| { | |
| global $evo_replace_outside_code_tags_callback; | |
| // Replace non matching blocks from temp strings like '?X219a33da9c1b8f4e335bffc015df8c96X?' back to original string like '<code>$some_var = "some value";</code>' | |
| $m = fix_non_matching_blocks( $m ); | |
| // Call real function with fixed blocks in matches: | |
| $result = call_user_func_array( $evo_replace_outside_code_tags_callback, array( $m ) ); | |
| // Clear temp var: | |
| unset( $evo_replace_outside_code_tags_callback ); | |
| return $result; | |
| } | |
| /** | |
| * Split a content by separators outside <code> and <pre> blocks | |
| * | |
| * @param string|array Separators | |
| * @param string Content | |
| * @param boolean TRUE - parenthesized expression of separator will be captured and returned as well | |
| * @return array The result of explode() function | |
| */ | |
| function split_outcode( $separators, $content, $capture_separator = false ) | |
| { | |
| // Check if the separators exists in content | |
| if( ! is_array( $separators ) ) | |
| { // Convert string to array with one element | |
| $separators = array( $separators ); | |
| } | |
| $separators_exists = false; | |
| if( is_array( $separators ) ) | |
| { // Find in array | |
| foreach( $separators as $separator ) | |
| { | |
| if( strpos( $content, $separator ) !== false ) | |
| { // Separator is found | |
| $separators_exists = true; | |
| break; | |
| } | |
| } | |
| } | |
| if( $separators_exists ) | |
| { // There are separators in content, Split the content: | |
| // Initialize temp values for replace the separators | |
| if( $capture_separator ) | |
| { | |
| $rplc_separators = array(); | |
| foreach( $separators as $s => $separator ) | |
| { | |
| $rplc_separators[] = '#separator'.$s.'='.md5( rand() ).'#'; | |
| } | |
| } | |
| else | |
| { | |
| $rplc_separators = '#separator='.md5( rand() ).'#'; | |
| } | |
| // Replace the content separators with temp value | |
| if( strpos( $content, '<code' ) !== false || strpos( $content, '<pre' ) !== false ) | |
| { // Call replace_separators_callback() on everything outside code/pre: | |
| $content = callback_on_non_matching_blocks( $content, | |
| '~<(code|pre)[^>]*>.*?</\1>~is', | |
| 'replace_content', array( $separators, $rplc_separators, 'str' ) ); | |
| } | |
| else | |
| { // No code/pre blocks, replace on the whole thing | |
| $content = str_replace( $separators, $rplc_separators, $content ); | |
| } | |
| if( $capture_separator ) | |
| { // Save the separators | |
| $split_regexp = '~('.implode( '|', $rplc_separators ).')~s'; | |
| $content_parts = preg_split( $split_regexp, $content, -1, PREG_SPLIT_DELIM_CAPTURE ); | |
| foreach( $content_parts as $c => $content_part ) | |
| { | |
| if( ( $s = array_search( $content_part, $rplc_separators ) ) !== false ) | |
| { // Replace original separator back | |
| $content_parts[ $c ] = $separators[ $s ]; | |
| } | |
| } | |
| return $content_parts; | |
| } | |
| else | |
| { // Return only splitted content(without separators) | |
| return explode( $rplc_separators, $content ); | |
| } | |
| } | |
| else | |
| { // No separators in content, Return whole content as one element of array | |
| return array( $content ); | |
| } | |
| } | |
| /** | |
| * Remove short tags like [image:], [video:] and etc. that are inside <p> blocks and before <br> and move them before the paragraph | |
| * | |
| * @param string Source content | |
| * @param string Search pattern | |
| * @param function Optional callback function that accepts search pattern and current paragraph as arguments and returns the new_paragraph | |
| * @param array Params | |
| * @return string Content | |
| */ | |
| function move_short_tags( $content, $pattern = NULL, $callback = NULL, $params = array() ) | |
| { | |
| $params = array_merge( array( | |
| 'check_code_block' => true, | |
| ), $params ); | |
| if( isset( $params['check_code_block'] ) && $params['check_code_block'] && ( ( stristr( $content, '<code' ) !== false ) || ( stristr( $content, '<pre' ) !== false ) ) ) | |
| { // Call $this->render_collection_data() on everything outside code/pre: | |
| $params['check_code_block'] = false; | |
| $content = callback_on_non_matching_blocks( $content, | |
| '~<(code|pre)[^>]*>.*?</\1>~is', | |
| 'move_short_tags', array( $pattern, $callback, $params ) ); | |
| return $content; | |
| } | |
| // Get individual paragraphs: | |
| preg_match_all( '#(<p[\s*|>])?.*?<(/p|br\s?/?)>#i', $content, $paragraphs ); | |
| if( is_null( $pattern ) ) | |
| { // Default pattern: | |
| $pattern = '#\[(image|video|audio|include|cblock|/?div|(parent:|item:[^:\]]+:)?(subscribe|emailcapture|compare|fields)):[^\]]+\]#i'; | |
| } | |
| foreach( $paragraphs[0] as $i => $current_paragraph ) | |
| { | |
| if( $callback ) | |
| { | |
| $new_paragraph = call_user_func( $callback, $pattern, $current_paragraph ); | |
| } | |
| else | |
| { | |
| // get short tags in each paragraph | |
| preg_match_all( $pattern, $current_paragraph, $matches ); | |
| $new_paragraph = $current_paragraph; | |
| if( $matches[0] ) | |
| { | |
| $new_paragraph = str_replace( $matches[0], '', $current_paragraph ); | |
| // convert to space | |
| $x = str_replace( "\xC2\xA0", ' ', $new_paragraph ); | |
| if( preg_match( '#^(<p[\s*|>])?\s*<(/p|br\s?/?)>$#i', $x ) === 1 ) | |
| { // Remove paragraph the if moving out the short tag will result to an empty paragraph: | |
| $new_paragraph = ''; | |
| } | |
| $new_paragraph = implode( '', $matches[0] ).$new_paragraph; | |
| } | |
| } | |
| $content = str_replace( $current_paragraph, $new_paragraph, $content ); | |
| } | |
| return $content; | |
| } | |
| /** | |
| * Make links clickable in a given text. | |
| * | |
| * It replaces only text which is not between <a> tags already. | |
| * | |
| * @todo dh> this should not replace links in tags! currently fails for something | |
| * like '<img src=" http://example.com/" />' (not usual though!) | |
| * fp> I am trying to address this by not replacing anything inside tags | |
| * fp> This should be replaced by a clean state machine (one single variable for current state) | |
| * | |
| * {@internal This function gets tested in misc.funcs.simpletest.php.}} | |
| * | |
| * @param string Text | |
| * @param string Url delimeter | |
| * @param string Callback function name | |
| * @param string Additional attributes for tag <a> | |
| * @param boolean TRUE to exclude links from header tags like h1, h2, etc. | |
| * @return string | |
| */ | |
| function make_clickable( $text, $moredelim = '&', $callback = 'make_clickable_callback', $additional_attrs = '', $exclude_headers = false ) | |
| { | |
| $r = ''; | |
| $inside_bracket_short_tag = false; | |
| $inside_tag = false; | |
| $in_a_tag = false; | |
| $in_code_tag = false; | |
| $in_tag_quote = false; | |
| $in_header_tag = false; | |
| $from_pos = 0; | |
| $i = 0; | |
| $n = strlen($text); | |
| // Not using callback_on_non_matching_blocks(), because it requires | |
| // wellformed HTML and the implementation below should be | |
| // faster and less memory intensive (tested for some example content) | |
| while( $i < $n ) | |
| { // Go through each char in string... (we will fast forward from tag to tag) | |
| if( $inside_bracket_short_tag ) | |
| { // State: We're currently inside bracket short tag like [image:], [emailcapture:], [fields:], [compare:] and etc.: | |
| if( $text[ $i ] == ']' ) | |
| { // End of bracket short tag: | |
| $inside_bracket_short_tag = false; | |
| $r .= substr( $text, $from_pos, $i - $from_pos + 1 ); | |
| $from_pos = $i + 1; | |
| } | |
| } | |
| elseif( $inside_tag ) | |
| { // State: We're currently inside some tag: | |
| switch( $text[$i] ) | |
| { | |
| case '>': | |
| if( $in_tag_quote ) | |
| { // This is in a quoted string so it doesn't really matter... | |
| break; | |
| } | |
| // end of tag: | |
| $inside_tag = false; | |
| $r .= substr($text, $from_pos, $i-$from_pos+1); | |
| $from_pos = $i+1; | |
| // $r .= '}'; | |
| break; | |
| case '"': | |
| case '\'': | |
| // This is the beginning or the end of a quoted string: | |
| if( ! $in_tag_quote ) | |
| { | |
| $in_tag_quote = $text[$i]; | |
| } | |
| elseif( $in_tag_quote == $text[$i] ) | |
| { | |
| $in_tag_quote = false; | |
| } | |
| break; | |
| } | |
| } | |
| elseif( $in_a_tag ) | |
| { // In a link but no longer inside <a>...</a> tag or any other embedded tag like <strong> or whatever | |
| switch( $text[$i] ) | |
| { | |
| case '<': | |
| if( strtolower(substr($text, $i+1, 3)) == '/a>' ) | |
| { // Ok, this is the end tag of the link: | |
| // $r .= substr($text, $from_pos, $i-$from_pos+4); | |
| // $from_pos = $i+4; | |
| $i += 4; | |
| // pre_dump( 'END A TAG: '.substr($text, $from_pos, $i-$from_pos) ); | |
| $r .= substr($text, $from_pos, $i-$from_pos); | |
| $from_pos = $i; | |
| $in_a_tag = false; | |
| $in_tag_quote = false; | |
| $in_header_tag = false; | |
| } | |
| break; | |
| } | |
| } | |
| elseif( $in_code_tag ) | |
| { // In a code but no longer inside <code>...</code> tag or any other embedded tag like <strong> or whatever | |
| switch( $text[$i] ) | |
| { | |
| case '<': | |
| if( strtolower(substr($text, $i+1, 5)) == '/code' ) | |
| { // Ok, this is the end tag of the code: | |
| // $r .= substr($text, $from_pos, $i-$from_pos+4); | |
| // $from_pos = $i+4; | |
| $i += 7; | |
| // pre_dump( 'END A TAG: '.substr($text, $from_pos, $i-$from_pos) ); | |
| $r .= substr($text, $from_pos, $i-$from_pos); | |
| $from_pos = $i; | |
| $in_code_tag = false; | |
| $in_tag_quote = false; | |
| $in_header_tag = false; | |
| } | |
| break; | |
| } | |
| } | |
| elseif( $in_header_tag ) | |
| { // In a code but no longer inside <h#>...</h#> tags or any other embedded tag like <strong> or whatever | |
| switch( $text[$i] ) | |
| { | |
| case '<': | |
| if( strtolower( substr( $text, $i+1, 3 ) ) == '/'.$in_header_tag ) | |
| { // Ok, this is the end tag of the header: | |
| $i += 5; | |
| $r .= substr( $text, $from_pos, $i - $from_pos ); | |
| $from_pos = $i; | |
| $in_code_tag = false; | |
| $in_tag_quote = false; | |
| $in_header_tag = false; | |
| } | |
| break; | |
| } | |
| } | |
| else | |
| { // State: we're not currently in any tag: | |
| // Find next tag opening: | |
| if( ( $b = strpos( $text, '[', $i ) ) !== false && | |
| $b < strpos( $text, '<', $i ) ) | |
| { // Check if a bracket '[]' short tag is really opening but not after html tag: | |
| // (we are finding here short tags like [image:], [emailcapture:], [fields:], [compare:] and etc., see full list in the Item->render_inline_tags()) | |
| $start_b = $b; | |
| $b++; | |
| $short_tag_name = ''; | |
| while( isset( $text[ $b ] ) && $text[ $b ] != ':' && $text[ $b ] != ']' ) | |
| { // Get short tag name between chars '[' and ( ':' or ']' ): | |
| $short_tag_name .= $text[ $b ]; | |
| $b++; | |
| } | |
| // We are inside bracket short tag if its name contains only letters: | |
| $inside_bracket_short_tag = preg_match( '/^[a-z]+$/', $short_tag_name ); | |
| if( $inside_bracket_short_tag ) | |
| { // Set index to call user func below between two inline short tags: | |
| $i = $start_b; | |
| } | |
| } | |
| if( ! $inside_bracket_short_tag ) | |
| { // Try to find opening HTML tag only if bracket short tag is not opened currently: | |
| $i = strpos($text, '<', $i); | |
| if( $i === false ) | |
| { // No more opening tags: | |
| break; | |
| } | |
| $inside_tag = true; | |
| $in_tag_quote = false; | |
| // s$r .= '{'.$text[$i+1]; | |
| if( ($text[$i+1] == 'a' || $text[$i+1] == 'A') && ctype_space($text[$i+2]) ) | |
| { // opening "A" tag | |
| $in_a_tag = true; | |
| } | |
| if( ( substr( $text, $i+1, 4 ) == 'code') ) | |
| { // opening "code" tag | |
| $in_code_tag = true; | |
| } | |
| if( $exclude_headers && preg_match( '/^h[1-6]$/', substr( $text, $i+1, 2 ), $h_match ) ) | |
| { // opening "h1" - "h6" tags: | |
| $in_header_tag = $h_match[0]; | |
| } | |
| } | |
| // Make the text before the opening < clickable: | |
| $r .= call_user_func_array( $callback, array( substr( $text, $from_pos, $i-$from_pos ), $moredelim, $additional_attrs ) ); | |
| $from_pos = $i; | |
| // $i += 2; | |
| } | |
| $i++; | |
| } | |
| // the remaining part: | |
| if( $in_a_tag ) | |
| { // may happen for invalid html: | |
| $r .= substr($text, $from_pos); | |
| } | |
| else | |
| { // Make remplacements in the remaining part: | |
| $r .= call_user_func_array( $callback, array( substr( $text, $from_pos ), $moredelim, $additional_attrs ) ); | |
| } | |
| return $r; | |
| } | |
| /** | |
| * Callback function for {@link make_clickable()}. | |
| * | |
| * original function: phpBB, extended here for AIM & ICQ | |
| * fplanque restricted :// to http:// and mailto:// | |
| * Fixed to not include trailing dot and comma. | |
| * | |
| * fp> I'm thinking of moving this into the autolinks plugin (only place where it's used) | |
| * and break it up into something more systematic. | |
| * | |
| * @param string Text | |
| * @param string Url delimeter | |
| * @param string Additional attributes for tag <a> | |
| * @return string The clickable text. | |
| */ | |
| function make_clickable_callback( $text, $moredelim = '&', $additional_attrs = '' ) | |
| { | |
| if( !empty( $additional_attrs ) ) | |
| { | |
| $additional_attrs = ' '.trim( $additional_attrs ); | |
| } | |
| // Add style class to break long urls: | |
| $additional_attrs = stripos( $additional_attrs, ' class="' ) === false | |
| ? $additional_attrs.' class="linebreak"' | |
| : preg_replace( '/ class="([^"]*)"/i', ' class="$1 linebreak"', $additional_attrs ); | |
| $pattern_domain = '([\p{L}0-9\-]+\.[\p{L}0-9\-.\~]+)'; // a domain name (not very strict) | |
| $text = preg_replace( | |
| /* Tblue> I removed the double quotes from the first RegExp because | |
| it made URLs in tag attributes clickable. | |
| See http://forums.b2evolution.net/viewtopic.php?p=92073 */ | |
| array( '#(^|[\s>\(]|\[url=)(https?|mailto)://([^"<>{}\s]+[^".,:;!\?<>{}\s\]\)])#i', | |
| '#(^|[\s>\(]|\[url=)aim:([^",<\s\]\)]+)#i', | |
| '#(^|[\s>\(]|\[url=)icq:(\d+)#i', | |
| '#(^|[\s>\(]|\[url=)www\.'.$pattern_domain.'([^"<>{}\s]*[^".,:;!\?\s\]\)])#i', | |
| '#(^|[\s>\(]|\[url=)([a-z0-9\-_.]+?)@'.$pattern_domain.'([^".,:;!\?&<\s\]\)]+)#i', ), | |
| array( '$1<a href="$2://$3"'.$additional_attrs.'>$2://$3</a>', | |
| '$1<a href="aim:goim?screenname=$2$3'.$moredelim.'message='.rawurlencode(T_('Hello')).'"'.$additional_attrs.'>$2$3</a>', | |
| '$1<a href="http://wwp.icq.com/scripts/search.dll?to=$2"'.$additional_attrs.'>$2</a>', | |
| '$1<a href="http://www.$2$3$4"'.$additional_attrs.'>www.$2$3$4</a>', | |
| '$1<a href="mailto:$2@$3$4"'.$additional_attrs.'>$2@$3$4</a>', ), | |
| $text ); | |
| return $text; | |
| } | |
| /***** // Formatting functions *****/ | |
| /** | |
| * Convert timestamp to MySQL/ISO format. | |
| * | |
| * @param integer UNIX timestamp | |
| * @return string Date formatted as "Y-m-d H:i:s" | |
| */ | |
| function date2mysql( $ts ) | |
| { | |
| if( $ts > 0 ) | |
| { // Allow only positive timestamp value: | |
| return date( 'Y-m-d H:i:s', $ts ); | |
| } | |
| else | |
| { // If timestamp is wrong(NULL or FALSE or negative value) use this mimimum date instead of default 1970-01-01 00:00:00, | |
| // because with negative timezone like -2:00 it may returns 1969-12-31 22:00:00 which creates MySQL error "Incorrect datetime value". | |
| return '2000-01-01 00:00:00'; | |
| } | |
| } | |
| /** | |
| * Convert a MYSQL date to a UNIX timestamp. | |
| * | |
| * @param string Date formatted as "Y-m-d H:i:s" | |
| * @param boolean true to use GM time | |
| * @return integer UNIX timestamp | |
| */ | |
| function mysql2timestamp( $m, $useGM = false ) | |
| { | |
| $func = $useGM ? 'gmmktime' : 'mktime'; | |
| return $func( | |
| intval( substr( $m, 11, 2 ) ), // hour | |
| intval( substr( $m, 14, 2 ) ), // minute | |
| intval( substr( $m, 17, 2 ) ), // second | |
| intval( substr( $m, 5, 2 ) ), // month | |
| intval( substr( $m, 8, 2 ) ), // day | |
| intval( substr( $m, 0, 4 ) ) ); // year | |
| } | |
| /** | |
| * Convert a MYSQL date -- WITHOUT the time -- to a UNIX timestamp | |
| * | |
| * @param string Date formatted as "Y-m-d" | |
| * @param boolean true to use GM time | |
| * @return integer UNIX timestamp | |
| */ | |
| function mysql2datestamp( $m, $useGM = false ) | |
| { | |
| $func = $useGM ? 'gmmktime' : 'mktime'; | |
| return $func( 0, 0, 0, substr($m,5,2), substr($m,8,2), substr($m,0,4) ); | |
| } | |
| /** | |
| * Format a MYSQL date to current locale date format. | |
| * | |
| * @param string MYSQL date YYYY-MM-DD HH:MM:SS | |
| */ | |
| function mysql2localedate( $mysqlstring ) | |
| { | |
| return mysql2date( locale_datefmt(), $mysqlstring ); | |
| } | |
| function mysql2localetime( $mysqlstring ) | |
| { | |
| return mysql2date( locale_timefmt(), $mysqlstring ); | |
| } | |
| function mysql2localedatetime( $mysqlstring ) | |
| { | |
| return mysql2date( locale_datefmt().' '.locale_timefmt(), $mysqlstring ); | |
| } | |
| function mysql2localedatetime_spans( $mysqlstring, $datefmt = NULL, $timefmt = NULL ) | |
| { | |
| if( is_null( $datefmt ) ) | |
| { | |
| $datefmt = locale_datefmt(); | |
| } | |
| if( is_null( $timefmt ) ) | |
| { | |
| $timefmt = locale_timefmt(); | |
| } | |
| return '<span class="date">' | |
| .mysql2date( $datefmt, $mysqlstring ) | |
| .'</span> <span class="time">' | |
| .mysql2date( $timefmt, $mysqlstring ) | |
| .'</span>'; | |
| } | |
| /** | |
| * Format a MYSQL date. | |
| * | |
| * @param string enhanced format string | |
| * @param string MYSQL date YYYY-MM-DD HH:MM:SS | |
| * @param boolean true to use GM time | |
| */ | |
| function mysql2date( $dateformatstring, $mysqlstring, $useGM = false ) | |
| { | |
| $m = $mysqlstring; | |
| if( empty($m) || ($m == '0000-00-00 00:00:00' ) ) | |
| return false; | |
| // Get a timestamp: | |
| $unixtimestamp = mysql2timestamp( $m ); | |
| return date_i18n( $dateformatstring, $unixtimestamp, $useGM ); | |
| } | |
| /** | |
| * Date internationalization: same as date() formatting but with i18n support. | |
| * | |
| * @todo dh> support for MySQL date format instead of $unixtimestamp? This would simplify callees, where currently mktime() is used. | |
| * @param string enhanced format string | |
| * @param integer UNIX timestamp | |
| * @param boolean true to use GM time | |
| */ | |
| function date_i18n( $dateformatstring, $unixtimestamp, $useGM = false ) | |
| { | |
| global $month, $month_abbrev, $weekday, $weekday_abbrev, $weekday_letter; | |
| global $localtimenow, $time_difference; | |
| if( $dateformatstring == 'isoZ' ) | |
| { // full ISO 8601 format | |
| $dateformatstring = 'Y-m-d\TH:i:s\Z'; | |
| } | |
| if( $useGM ) | |
| { // We want a Greenwich Meridian time: | |
| // TODO: dh> what's the point of the substraction? UNIX timestamp should contain no time_difference in the first place?! Otherwise it should be substracted for !$useGM, too. | |
| // TODO: dh> Why does $useGM do not get the special symbols handling? | |
| $r = gmdate($dateformatstring, ($unixtimestamp - $time_difference)); | |
| } | |
| else | |
| { // We want default timezone time: | |
| /* | |
| Special symbols: | |
| 'b': wether it's today (1) or not (0) | |
| 'l': weekday | |
| 'D': weekday abbrev | |
| 'e': weekday letter | |
| 'F': month | |
| 'M': month abbrev | |
| */ | |
| #echo $dateformatstring, '<br />'; | |
| // protect special symbols, that date() would need proper locale set for | |
| $protected_dateformatstring = preg_replace( '/(?<!\\\)([blDeFM])/', '@@@\\\$1@@@', $dateformatstring ); | |
| #echo $protected_dateformatstring, '<br />'; | |
| $r = date( $protected_dateformatstring, $unixtimestamp ); | |
| if( $protected_dateformatstring != $dateformatstring ) | |
| { // we had special symbols, replace them | |
| $istoday = ( date('Ymd',$unixtimestamp) == date('Ymd',$localtimenow) ) ? '1' : '0'; | |
| $datemonth = date('m', $unixtimestamp); | |
| $dateweekday = date('w', $unixtimestamp); | |
| // replace special symbols | |
| $r = str_replace( array( | |
| '@@@b@@@', | |
| '@@@l@@@', | |
| '@@@D@@@', | |
| '@@@e@@@', | |
| '@@@F@@@', | |
| '@@@M@@@', | |
| ), | |
| array( $istoday, | |
| trim(T_($weekday[$dateweekday])), | |
| trim(T_($weekday_abbrev[$dateweekday])), | |
| trim(T_($weekday_letter[$dateweekday])), | |
| trim(T_($month[$datemonth])), | |
| trim(T_($month_abbrev[$datemonth])) ), | |
| $r ); | |
| } | |
| } | |
| return $r; | |
| } | |
| /** | |
| * Add given # of days to a timestamp | |
| * | |
| * @param integer timestamp | |
| * @param integer days | |
| * @return integer timestamp | |
| */ | |
| function date_add_days( $timestamp, $days ) | |
| { | |
| return mktime( date('H',$timestamp), date('m',$timestamp), date('s',$timestamp), | |
| date('m',$timestamp), date('d',$timestamp)+$days, date('Y',$timestamp) ); | |
| } | |
| /** | |
| * Format dates into a string in a way similar to sprintf() | |
| */ | |
| function date_sprintf( $string, $timestamp ) | |
| { | |
| global $date_sprintf_timestamp; | |
| $date_sprintf_timestamp = $timestamp; | |
| return preg_replace_callback( '/%\{(.*?)\}/', 'date_sprintf_callback', $string ); | |
| } | |
| function date_sprintf_callback( $matches ) | |
| { | |
| global $date_sprintf_timestamp; | |
| return date_i18n( $matches[1], $date_sprintf_timestamp ); | |
| } | |
| /** | |
| * Get date name when date was happened | |
| * | |
| * @param integer Timestamp | |
| * @return string Name of date (Today, Yesterday, x days ago, x months ago, x years ago) | |
| */ | |
| function date_ago( $timestamp ) | |
| { | |
| global $servertimenow; | |
| $days = floor( ( $servertimenow - $timestamp ) / 86400 ); | |
| $months = ceil( $days / 31 ); | |
| if( $days < 1 ) | |
| { // Today | |
| return T_('Today'); | |
| } | |
| elseif( $days == 1 ) | |
| { // Yesterday | |
| return T_('Yesterday'); | |
| } | |
| elseif( $days > 1 && $days <= 31 ) | |
| { // Days | |
| return sprintf( T_('%s days ago'), $days ); | |
| } | |
| elseif( $days > 31 && $months <= 12 ) | |
| { // Months | |
| return sprintf( $months == 1 ? T_('%s month ago') : T_('%s months ago'), $months ); | |
| } | |
| else | |
| { // Years | |
| $years = floor( $months / 12 ); | |
| return sprintf( $years == 1 ? T_('%s year ago') : T_('%s years ago'), $years ); | |
| } | |
| } | |
| /** | |
| * Convert seconds to readable period | |
| * | |
| * @param integer Seconds | |
| * @param boolean TRUE to use short format(only first letter of period name) | |
| * @return string Readable time period | |
| */ | |
| function seconds_to_period( $seconds, $short_format = false ) | |
| { | |
| $periods = array( | |
| array( 31536000, T_('1 year'), T_('%s years'), /* TRANS: Short for "1year" */ T_('%sy') ), // 365 days | |
| array( 2592000, T_('1 month'), T_('%s months'), /* TRANS: Short for "1month" */ T_('%sm') ), // 30 days | |
| array( 86400, T_('1 day'), T_('%s days'), /* TRANS: Short for "1day" */ T_('%sd') ), | |
| array( 3600, T_('1 hour'), T_('%s hours'), /* TRANS: Short for "1hour" */ T_('%sh') ), | |
| array( 60, T_('1 minute'), T_('%s minutes'), /* TRANS: Short for "1minute" */T_('%smn') ), | |
| array( 1, T_('1 second'), T_('%s seconds'), /* TRANS: Short for "1second" */T_('%ss') ), | |
| ); | |
| foreach( $periods as $p_info ) | |
| { | |
| $period_value = intval( $seconds / $p_info[0] * 10 ) /10; | |
| if( $period_value >= 1 ) | |
| { // Stop on this period | |
| if( $short_format ) | |
| { // Use short format: | |
| $period_text = sprintf( $p_info[3], $period_value ); | |
| } | |
| elseif( $period_value == 1 ) | |
| { // One unit of period | |
| $period_text = $p_info[1]; | |
| } | |
| else | |
| { // Two and more units of period | |
| $period_text = sprintf( $p_info[2], $period_value ); | |
| } | |
| break; | |
| } | |
| } | |
| if( !isset( $period_text ) ) | |
| { // 0 seconds | |
| $period_text = $short_format ? sprintf( T_('%ss'), 0 ) : sprintf( T_('%s seconds'), 0 ); | |
| } | |
| return $period_text; | |
| } | |
| /** | |
| * Converts an ISO 8601 date to MySQL DateTime format. | |
| * | |
| * @param string date and time in ISO 8601 format {@link http://en.wikipedia.org/wiki/ISO_8601}. | |
| * @return string date and time in MySQL DateTime format Y-m-d H:i:s. | |
| */ | |
| function iso8601_to_datetime( $iso_date ) | |
| { | |
| return preg_replace('#([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(Z|[\+|\-][0-9]{2,4}){0,1}#', '$1-$2-$3 $4:$5:$6', $iso_date); | |
| } | |
| /** | |
| * Converts a MySQL DateTime to ISO 8601 date format. | |
| * | |
| * @param string date and time in MySQL DateTime format Y-m-d H:i:s | |
| * @return string date and time in ISO 8601 format {@link http://en.wikipedia.org/wiki/ISO_8601}. | |
| */ | |
| function datetime_to_iso8601( $datetime, $useGM = false ) | |
| { | |
| $iso_date = mysql2date('U', $datetime); | |
| if( $useGM ) | |
| { | |
| $iso_date = gmdate('Ymd', $iso_date).'T'.gmdate('H:i:s', $iso_date); | |
| } | |
| else | |
| { | |
| $iso_date = date('Ymd', $iso_date).'T'.date('H:i:s', $iso_date); | |
| } | |
| return $iso_date; | |
| } | |
| /** | |
| * | |
| * @param integer year | |
| * @param integer month (0-53) | |
| * @param integer 0 for sunday, 1 for monday | |
| */ | |
| function get_start_date_for_week( $year, $week, $startofweek ) | |
| { | |
| $new_years_date = mktime( 0, 0, 0, 1, 1, $year ); | |
| $weekday = date('w', $new_years_date); | |
| // echo '<br> 1st day is a: '.$weekday; | |
| // How many days until start of week: | |
| $days_to_new_week = (7 - $weekday + $startofweek) % 7; | |
| // echo '<br> days to new week: '.$days_to_new_week; | |
| // We now add the required number of days to find the 1st sunday/monday in the year: | |
| //$first_week_start_date = $new_years_date + $days_to_new_week * 86400; | |
| //echo '<br> 1st week starts on '.date( 'Y-m-d H:i:s', $first_week_start_date ); | |
| // We add the number of requested weeks: | |
| // This will fail when passing to Daylight Savings Time: $date = $first_week_start_date + (($week-1) * 604800); | |
| $date = mktime( 0, 0, 0, 1, $days_to_new_week + 1 + ($week-1) * 7, $year ); | |
| // echo '<br> week '.$week.' starts on '.date( 'Y-m-d H:i:s', $date ); | |
| return $date; | |
| } | |
| /** | |
| * Get start and end day of a week, based on day f the week and start-of-week | |
| * | |
| * Used by Calendar | |
| * | |
| * @param date | |
| * @param integer 0 for Sunday, 1 for Monday | |
| */ | |
| function get_weekstartend( $date, $startOfWeek ) | |
| { | |
| while( date('w', $date) <> $startOfWeek ) | |
| { | |
| // echo '<br />'.date('Y-m-d H:i:s w', $date).' - '.$startOfWeek; | |
| // mktime is needed so calculations work for DST enabling. Example: March 30th 2008, start of week 0 sunday | |
| $date = mktime( 0, 0, 0, date('m',$date), date('d',$date)-1, date('Y',$date) ); | |
| } | |
| // echo '<br />'.date('Y-m-d H:i:s w', $date).' = '.$startOfWeek; | |
| $week['start'] = $date; | |
| $week['end'] = $date + 604800; // 7 days | |
| // pre_dump( 'weekstartend: ', date( 'Y-m-d', $week['start'] ), date( 'Y-m-d', $week['end'] ) ); | |
| return( $week ); | |
| } | |
| /** | |
| * Get datetime rounded to lower minute. This is meant to remove seconds and | |
| * leverage MySQL's query cache by having SELECT queries remain identical for 60 seconds instead of just 1. | |
| * | |
| * @param integer UNIX timestamp | |
| * @param string Format (defaults to "Y-m-d H:i"). Use "U" for UNIX timestamp. | |
| */ | |
| function remove_seconds($timestamp, $format = 'Y-m-d H:i') | |
| { | |
| return date($format, floor($timestamp/60)*60); | |
| } | |
| /** | |
| * Convert from seconds to months, days, hours, minutes and seconds | |
| * | |
| * @param integer duration in seconds | |
| * @return array of [ years, months, days, hours, minutes, seconds ] | |
| */ | |
| function get_duration_fields( $duration ) | |
| { | |
| $result = array(); | |
| $year_seconds = 31536000; // 1 year | |
| $years = floor( $duration / $year_seconds ); | |
| $duration = $duration - $years * $year_seconds; | |
| $result[ 'years' ] = $years; | |
| $month_seconds = 2592000; // 1 month | |
| $months = floor( $duration / $month_seconds ); | |
| $duration = $duration - $months * $month_seconds; | |
| $result[ 'months' ] = $months; | |
| $day_seconds = 86400; // 1 day | |
| $days = floor( $duration / $day_seconds ); | |
| $duration = $duration - $days * $day_seconds; | |
| $result[ 'days' ] = $days; | |
| $hour_seconds = 3600; // 1 hour | |
| $hours = floor( $duration / $hour_seconds ); | |
| $duration = $duration - $hours * $hour_seconds; | |
| $result[ 'hours' ] = $hours; | |
| $minute_seconds = 60; // 1 minute | |
| $minutes = floor( $duration / $minute_seconds ); | |
| $duration = $duration - $minutes * $minute_seconds; | |
| $result[ 'minutes' ] = $minutes; | |
| $result[ 'seconds' ] = $duration; | |
| return $result; | |
| } | |
| /** | |
| * Get a title of duration | |
| * | |
| * @param integer Duration in seconds | |
| * @param array Titles | |
| * @return string Duration title | |
| */ | |
| function get_duration_title( $duration, $titles = array() ) | |
| { | |
| $titles = array_merge( array( | |
| 'year' => T_('Last %d years'), | |
| 'month' => T_('Last %d months'), | |
| 'day' => T_('Last %d days'), | |
| 'hour' => T_('Last %d hours'), | |
| 'minute' => T_('Last %d minutes'), | |
| 'second' => T_('Last %d seconds'), | |
| ), $titles ); | |
| $delay_fields = get_duration_fields( $duration ); | |
| if( ! empty( $delay_fields[ 'years' ] ) ) | |
| { // Years | |
| return sprintf( $titles['year'], $delay_fields[ 'years' ] ); | |
| } | |
| elseif( ! empty( $delay_fields[ 'months' ] ) ) | |
| { // Months | |
| return sprintf( $titles['month'], $delay_fields[ 'months' ] ); | |
| } | |
| elseif( ! empty( $delay_fields[ 'days' ] ) ) | |
| { // Days | |
| return sprintf( $titles['day'], $delay_fields[ 'days' ] ); | |
| } | |
| elseif( ! empty( $delay_fields[ 'hours' ] ) ) | |
| { // Hours | |
| return sprintf( $titles['hour'], $delay_fields[ 'hours' ] ); | |
| } | |
| elseif( ! empty( $delay_fields[ 'minutes' ] ) ) | |
| { // Minutes | |
| return sprintf( $titles['minute'], $delay_fields[ 'minutes' ] ); | |
| } | |
| else | |
| { // Seconds | |
| return sprintf( $titles['second'], $delay_fields[ 'seconds' ] ); | |
| } | |
| } | |
| function get_lastseen_date( $date, $view = 'exact_date', $cheat = 0 ) | |
| { | |
| $result = mysql2localedate( $date ); | |
| if( $view == 'blurred_date' ) | |
| { | |
| $result = (int)( ( ( time() - strtotime( $date ) ) / 86400 ) - $cheat); | |
| if( $result < 0 ) | |
| { | |
| $result = 0; | |
| } | |
| if( $result < 3 ) | |
| { | |
| $result = T_('less than 3 days ago'); | |
| } | |
| elseif( $result < 7 ) | |
| { | |
| $result = T_('less than a week ago'); | |
| } | |
| elseif( $result < 30 ) | |
| { | |
| $result = T_('less than a month ago'); | |
| } | |
| elseif( $result < 90 ) | |
| { | |
| $result = T_('less than 3 months ago'); | |
| } | |
| else | |
| { | |
| $result = T_('more than 3 months ago'); | |
| } | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Validate variable | |
| * | |
| * @param string param name | |
| * @param string validator function name | |
| * @param boolean true if variable value can't be empty | |
| * @param custom error message | |
| * @return boolean true if OK | |
| */ | |
| function param_validate( $variable, $validator, $required = false, $custom_msg = NULL ) | |
| { | |
| /* Tblue> Note: is_callable() does not check whether a function is | |
| * disabled (http://www.php.net/manual/en/function.is-callable.php#79151). | |
| */ | |
| if( ! is_callable( $validator ) ) | |
| { | |
| debug_die( 'Validator function '.$validator.'() is not callable!' ); | |
| } | |
| if( ! isset( $GLOBALS[$variable] ) ) | |
| { // Variable not set, we cannot handle this using the validator function... | |
| if( $required ) | |
| { // Add error: | |
| param_check_not_empty( $variable, $custom_msg ); | |
| return false; | |
| } | |
| return true; | |
| } | |
| if( $GLOBALS[$variable] === '' && ! $required ) | |
| { // Variable is empty or not set. That's fine since it isn't required: | |
| return true; | |
| } | |
| $msg = $validator( $GLOBALS[$variable] ); | |
| if( !empty( $msg ) ) | |
| { | |
| if( !empty( $custom_msg ) ) | |
| { | |
| $msg = $custom_msg; | |
| } | |
| param_error( $variable, $msg ); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Checks if the param is a decimal number | |
| * | |
| * @param string decimal to check | |
| * @return boolean true if OK | |
| */ | |
| function is_decimal( $decimal ) | |
| { | |
| return preg_match( '#^[0-9]*(\.[0-9]+)?$#', $decimal ); | |
| } | |
| /** | |
| * Checks if the param is an integer (no float, e.g. 3.14). | |
| * | |
| * @param string number to check | |
| * @return boolean true if OK | |
| */ | |
| function is_number( $number ) | |
| { | |
| return preg_match( '#^[0-9]+$#', $number ); | |
| } | |
| /** | |
| * Check that email address looks valid. | |
| * | |
| * @param string email address to check | |
| * @param string Format to use ('simple', 'rfc') | |
| * 'simple': | |
| * Single email address. | |
| * 'rfc': | |
| * Full email address, may include name (RFC2822) | |
| * - example@example.org | |
| * - Me <example@example.org> | |
| * - "Me" <example@example.org> | |
| * @param boolean Return the match or boolean | |
| * | |
| * @return bool|array Either true/false or the match (see {@link $return_match}) | |
| */ | |
| function is_email( $email, $format = 'simple', $return_match = false ) | |
| { | |
| #$chars = "/^([a-z0-9_]|\\-|\\.)+@(([a-z0-9_]|\\-)+\\.)+[a-z]{2,4}\$/i"; | |
| switch( $format ) | |
| { | |
| case 'rfc': | |
| case 'rfc2822': | |
| /** | |
| * Regexp pattern converted from: http://www.regexlib.com/REDetails.aspx?regexp_id=711 | |
| * Extended to allow escaped quotes. | |
| */ | |
| $pattern_email = '/^ | |
| ( | |
| (?>[a-zA-Z\d!\#$%&\'*+\-\/=?^_`{|}~]+\x20* | |
| |"( \\\" | (?=[\x01-\x7f])[^"\\\] | \\[\x01-\x7f] )*"\x20*)* # Name | |
| (<) | |
| )? | |
| ( | |
| (?!\.)(?>\.?[a-zA-Z\d!\#$%&\'*+\-\/=?^_`{|}~]+)+ | |
| |"( \\\" | (?=[\x01-\x7f])[^"\\\] | \\[\x01-\x7f] )* " # quoted mailbox name | |
| ) | |
| @ | |
| ( | |
| ((?!-)[a-zA-Z\d\-]+(?<!-)\.)+[a-zA-Z]{2,} | |
| | | |
| \[( | |
| ( (?(?<!\[)\.)(25[0-5] | 2[0-4]\d | [01]?\d?\d) ){4} | |
| | | |
| [a-zA-Z\d\-]*[a-zA-Z\d]:( (?=[\x01-\x7f])[^\\\[\]] | \\[\x01-\x7f] )+ | |
| )\] | |
| ) | |
| (?(3)>) # match ">" if it was there | |
| $/x'; | |
| break; | |
| case 'simple': | |
| default: | |
| // '/^\S+@[^\.\s]\S*\.[a-z]{2,}$/i' | |
| $pattern_email = '~^(([_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,}))$~i'; | |
| break; | |
| } | |
| if( strpos( $email, '@' ) !== false && strpos( $email, '.' ) !== false ) | |
| { | |
| if( $return_match ) | |
| { | |
| preg_match( $pattern_email, $email, $match ); | |
| return $match; | |
| } | |
| else | |
| { | |
| return (bool)preg_match( $pattern_email, $email ); | |
| } | |
| } | |
| else | |
| { | |
| return $return_match ? array() : false; | |
| } | |
| } | |
| /** | |
| * Checks if the phone number is valid | |
| * | |
| * @param string phone number to check | |
| * @return boolean true if OK | |
| */ | |
| function is_phone( $phone ) | |
| { | |
| return preg_match( '|^\+?[\-*#/(). 0-9]+$|', $phone ); | |
| } | |
| /** | |
| * Checks if the url is valid | |
| * | |
| * @param string url to check | |
| * @return boolean true if OK | |
| */ | |
| function is_url( $url ) | |
| { | |
| if( validate_url( $url, 'posting', false ) ) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Checks if the word is valid | |
| * | |
| * @param string word to check | |
| * @return boolean true if OK | |
| */ | |
| function is_word( $word ) | |
| { | |
| return preg_match( '#^[A-Za-z]+$#', $word ); | |
| } | |
| /** | |
| * Check if the login is valid (in terms of allowed chars) | |
| * | |
| * @param string login | |
| * @return boolean|string TRUE if OK, FALSE if error, special error cases: 'usr', 'long' | |
| */ | |
| function is_valid_login( $login, $force_strict_logins = false ) | |
| { | |
| global $Settings; | |
| $strict_logins = isset( $Settings ) ? $Settings->get('strict_logins') : 1; | |
| // NOTE: in some places usernames are typed in by other users (messaging) or admins. | |
| // Having cryptic logins with hard to type letters is a PITA. | |
| // Step 1 | |
| // Forbid the following characters in logins | |
| if( preg_match( '~[\'"><@&\s]~', $login ) ) | |
| { // WARNING: allowing ' or " or > or < will open security issues! | |
| // NOTE: allowing @ will make some "average" users use their email address (not good for their spam health) | |
| // NOTE: we do not allow whitespace in logins | |
| return false; | |
| } | |
| // Step 2 | |
| if( ($strict_logins || $force_strict_logins) && ! preg_match( '~^[A-Za-z0-9_.\-]+$~', $login ) ) | |
| { // WARNING: allowing special chars like latin 1 accented chars ( \xDF-\xF6\xF8-\xFF ) will create issues with | |
| // user media directory names (tested on Max OS X) -- Do no allow any of this until we have a clean & safe media dir name generator. | |
| // fp> TODO: check why a dash '-' prevents renaming the fileroot | |
| return false; | |
| } | |
| elseif( ! $strict_logins ) | |
| { // We allow any character that is not explicitly forbidden in Step 1 | |
| // Enforce additional limitations | |
| $login = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '', $login ); // Kill octets | |
| $login = preg_replace( '/&.+?;/', '', $login ); // Kill entities | |
| } | |
| // Step 3 | |
| // Special case, the login is valid however we forbid it's usage. | |
| // param_check_valid_login() will display a special error message in this case. | |
| if( preg_match( '~^usr_~', $login ) ) | |
| { // Logins cannot start with 'usr_', this prefix is reserved for system use | |
| // We create user media directories for users with non-ASCII logins in format /media/users/usr_55/, where 55 is user ID | |
| return 'usr'; | |
| } | |
| // Step 4 | |
| // To avoid MySQL erro on insert long data | |
| if( utf8_strlen( $login ) > 20 ) | |
| { // Don't allow long login: | |
| return 'long'; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Checks if the color is valid | |
| * Allowed formats: #09ABEF, rgba(100,200,255,0.99) | |
| * | |
| * @param string color to check | |
| * @return boolean true if OK | |
| */ | |
| function is_color( $color ) | |
| { | |
| return preg_match( '~^(#([a-f0-9]{3}){1,2}|rgba\(\d{1,3},\d{1,3},\d{1,3},(1|0(\.\d{1,2})?)\))$~i', $color ); | |
| } | |
| /** | |
| * Check if the login is valid (user exists) | |
| * | |
| * @param string login | |
| * @return boolean true if OK | |
| */ | |
| function user_exists( $login ) | |
| { | |
| global $DB; | |
| $SQL = new SQL( 'Get a count of users with login '.$login ); | |
| $SQL->SELECT( 'COUNT(*)' ); | |
| $SQL->FROM( 'T_users' ); | |
| $SQL->WHERE( 'user_login = '.$DB->quote( $login ) ); | |
| $var = $DB->get_var( $SQL ); | |
| return $var > 0 ? true : false; // PHP4 compatibility | |
| } | |
| /** | |
| * Are we running on a Windows server? | |
| */ | |
| function is_windows() | |
| { | |
| return ( strtoupper(substr(PHP_OS,0,3)) == 'WIN' ); | |
| } | |
| /** | |
| * Get all "a" tags from the given content | |
| * | |
| * @param string content | |
| * @return array all <a../a> part from the given content | |
| */ | |
| function get_atags( $content ) | |
| { | |
| $tags = array(); | |
| if( preg_match_all( '#(<a[^>]+>(.*?)</a>|<a.+>(.*?)</a>)#i', $content, $result ) ) | |
| { | |
| $tags = $result[0]; | |
| } | |
| return $tags; | |
| } | |
| /** | |
| * Add class to an html tag | |
| * | |
| * @param string HTML content | |
| * @param string Class to add to the tag in the HTML | |
| * @param integer Number of tags to add the class to | |
| * @return string HTML content with the added class | |
| */ | |
| function add_tag_class( $content, $class, $limit = 1 ) | |
| { | |
| global $add_class; | |
| $add_class = $class; | |
| $content = preg_replace_callback( '/<[^\/].*?>/i', 'callback_add_tag_class', | |
| $content, $limit ); | |
| unset( $add_class ); | |
| return $content; | |
| } | |
| function callback_add_tag_class( $matches ) | |
| { | |
| global $add_class; | |
| // Check if class attribute is already defined | |
| if( preg_match( '/\sclass\s*=/i', $matches[0] ) ) | |
| { // Insert class | |
| //$content = preg_replace( '/(<.*)(class\s*=\s*")(.*)"/i', '$1$2$3 '.$class.'"', $content, $limit ); | |
| return preg_replace( '/(<[a-zA-z_:]\s*?.*?\s*?class=")(.*?)(".*?>)/i', '$1$2 '.$add_class.'$3', $matches[0] ); | |
| } | |
| else | |
| { | |
| return preg_replace( '/>/i', ' class="'.$add_class.'"$1>', $matches[0] ); | |
| } | |
| } | |
| /** | |
| * Get all "img" tags from the given content | |
| * | |
| * @param string content | |
| * @return array all <img../img> part from the given content | |
| */ | |
| function get_imgtags( $content ) | |
| { | |
| $tag = 'img'; | |
| $regexp = '{<'.$tag.'[^>]*[ (</'.$tag.'>) | (/>) ]}'; | |
| preg_match_all( $regexp, $content, $result ); | |
| return $result[0]; | |
| } | |
| /** | |
| * Get all urls from the given content | |
| * | |
| * @param string content | |
| * @return array all url from content | |
| */ | |
| function get_urls( $content ) | |
| { | |
| $regexp = '^(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2,4}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:\/(?:[-\w~!$+|.,;=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,;*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,;*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,;*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,;*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,;*:=]|%[a-f\d]{2})*)?^'; | |
| preg_match_all( $regexp, $content, $result ); | |
| return $result[0]; | |
| } | |
| function xmlrpc_getposttitle($content) | |
| { | |
| global $post_default_title; | |
| if (preg_match('/<title>(.+?)<\/title>/is', $content, $matchtitle)) | |
| { | |
| $post_title = $matchtitle[1]; | |
| } | |
| else | |
| { | |
| $post_title = $post_default_title; | |
| } | |
| return($post_title); | |
| } | |
| /** | |
| * Also used by post by mail | |
| * | |
| * @deprecated by xmlrpc_getpostcategories() | |
| */ | |
| function xmlrpc_getpostcategory($content) | |
| { | |
| if (preg_match('/<category>([0-9]+?)<\/category>/is', $content, $matchcat)) | |
| { | |
| return $matchcat[1]; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Extract categories out of "<category>" tag from $content. | |
| * | |
| * NOTE: w.bloggar sends something like "<category>00000013,00000001,00000004,</category>" to | |
| * blogger.newPost. | |
| * | |
| * @return false|array | |
| */ | |
| function xmlrpc_getpostcategories($content) | |
| { | |
| $cats = array(); | |
| if( preg_match('~<category>(\d+\s*(,\s*\d*)*)</category>~i', $content, $match) ) | |
| { | |
| $cats = preg_split('~\s*,\s*~', $match[1], -1, PREG_SPLIT_NO_EMPTY); | |
| foreach( $cats as $k => $v ) | |
| { | |
| $cats[$k] = (int)$v; | |
| } | |
| } | |
| return $cats; | |
| } | |
| /* | |
| * xmlrpc_removepostdata(-) | |
| */ | |
| function xmlrpc_removepostdata($content) | |
| { | |
| $content = preg_replace('/<title>(.*?)<\/title>/si', '', $content); | |
| $content = preg_replace('/<category>(.*?)<\/category>/si', '', $content); | |
| $content = trim($content); | |
| return($content); | |
| } | |
| /** | |
| * Echo the XML-RPC call Result and optionally log into file | |
| * | |
| * @param object XMLRPC response object | |
| * @param boolean true to echo | |
| * @param mixed File resource or == '' for no file logging. | |
| */ | |
| function xmlrpc_displayresult( $result, $display = true, $log = '' ) | |
| { | |
| if( ! $result ) | |
| { // We got no response: | |
| if( $display ) echo T_('No response!')."<br />\n"; | |
| return false; | |
| } | |
| if( $result->faultCode() ) | |
| { // We got a remote error: | |
| if( $display ) echo T_('Remote error'), ': ', $result->faultString(), ' (', $result->faultCode(), ")<br />\n"; | |
| debug_fwrite($log, $result->faultCode().' -- '.$result->faultString()); | |
| return false; | |
| } | |
| // We'll display the response: | |
| $val = $result->value(); | |
| $value = xmlrpc_decode_recurse($result->value()); | |
| if( is_array($value) ) | |
| { | |
| $out = ''; | |
| foreach($value as $l_value) | |
| { | |
| if( is_array( $l_value ) ) | |
| { | |
| $out .= ' ['; | |
| foreach( $l_value as $lv_key => $lv_val ) | |
| { | |
| $out .= $lv_key.' => '.( is_array( $lv_val ) ? '{'.implode( '; ', $lv_val ).'}' : $lv_val ).'; '; | |
| } | |
| $out .= '] '; | |
| } | |
| else | |
| { | |
| $out .= ' ['.$l_value.'] '; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| $out = $value; | |
| } | |
| debug_fwrite($log, $out); | |
| if( $display ) echo T_('Response').': '.$out."<br />\n"; | |
| return $value; | |
| } | |
| /** | |
| * Log the XML-RPC call Result into LOG object | |
| * | |
| * @param object XMLRPC response object | |
| * @param Log object to add messages to | |
| * @return boolean true = success, false = error | |
| */ | |
| function xmlrpc_logresult( $result, & $message_Log, $log_payload = true ) | |
| { | |
| if( ! $result ) | |
| { // We got no response: | |
| $message_Log->add( T_('No response!'), 'error' ); | |
| return false; | |
| } | |
| if( $result->faultCode() ) | |
| { // We got a remote error: | |
| $message_Log->add( T_('Remote error').': '.$result->faultString().' ('.$result->faultCode().')', 'error' ); | |
| return false; | |
| } | |
| if( $log_payload ) | |
| { | |
| // We got a response: | |
| $value = xmlrpc_decode_recurse($result->value()); | |
| if( is_array($value) ) | |
| { | |
| $out = ''; | |
| foreach($value as $l_value) | |
| { | |
| $out .= ' ['.var_export($l_value, true).'] '; | |
| } | |
| } | |
| else | |
| { | |
| $out = $value; | |
| } | |
| $message_Log->add( T_('Response').': '.$out, 'success' ); | |
| } | |
| return true; | |
| } | |
| function debug_fopen($filename, $mode) { | |
| global $debug; | |
| if ($debug == 1 && ( !empty($filename) ) ) | |
| { | |
| $fp = fopen($filename, $mode); | |
| return $fp; | |
| } else { | |
| return false; | |
| } | |
| } | |
| function debug_fwrite($fp, $string) | |
| { | |
| global $debug; | |
| if( $debug && $fp ) | |
| { | |
| fwrite($fp, $string); | |
| } | |
| } | |
| function debug_fclose($fp) | |
| { | |
| global $debug; | |
| if( $debug && $fp ) | |
| { | |
| fclose($fp); | |
| } | |
| } | |
| /** | |
| * Wrap pre tag around {@link var_dump()} for better debugging. | |
| * | |
| * @param $var__var__var__var__,... mixed variable(s) to dump | |
| * @return true | |
| */ | |
| function pre_dump( $var__var__var__var__ ) | |
| { | |
| global $is_cli; | |
| #echo 'pre_dump(): '.debug_get_backtrace(); // see where a pre_dump() comes from | |
| $func_num_args = func_num_args(); | |
| $count = 0; | |
| if( ! empty($is_cli) ) | |
| { // CLI, no encoding of special chars: | |
| $count = 0; | |
| foreach( func_get_args() as $lvar ) | |
| { | |
| var_dump($lvar); | |
| $count++; | |
| if( $count < $func_num_args ) | |
| { // Put newline between arguments | |
| echo "\n"; | |
| } | |
| } | |
| } | |
| elseif( function_exists('xdebug_var_dump') ) | |
| { // xdebug already does fancy displaying: | |
| // no limits: | |
| $old_var_display_max_children = @ini_set('xdebug.var_display_max_children', -1); // default: 128 | |
| $old_var_display_max_data = @ini_set('xdebug.var_display_max_data', -1); // max string length; default: 512 | |
| $old_var_display_max_depth = @ini_set('xdebug.var_display_max_depth', -1); // default: 3 | |
| echo "\n<div style=\"padding:1ex;border:1px solid #00f;\">\n"; | |
| foreach( func_get_args() as $lvar ) | |
| { | |
| xdebug_var_dump($lvar); | |
| $count++; | |
| if( $count < $func_num_args ) | |
| { // Put HR between arguments | |
| echo "<hr />\n"; | |
| } | |
| } | |
| echo '</div>'; | |
| // restore xdebug settings: | |
| @ini_set('xdebug.var_display_max_children', $old_var_display_max_children); | |
| @ini_set('xdebug.var_display_max_data', $old_var_display_max_data); | |
| @ini_set('xdebug.var_display_max_depth', $old_var_display_max_depth); | |
| } | |
| else | |
| { | |
| $orig_html_errors = @ini_set('html_errors', 0); // e.g. xdebug would use fancy html, if this is on; we catch (and use) xdebug explicitly above, but just in case | |
| echo "\n<pre style=\"padding:1ex;border:1px solid #00f;overflow:auto\">\n"; | |
| foreach( func_get_args() as $lvar ) | |
| { | |
| ob_start(); | |
| var_dump($lvar); // includes "\n"; do not use var_export() because it does not detect recursion by design | |
| $buffer = ob_get_contents(); | |
| ob_end_clean(); | |
| echo htmlspecialchars($buffer); | |
| $count++; | |
| if( $count < $func_num_args ) | |
| { // Put HR between arguments | |
| echo "<hr />\n"; | |
| } | |
| } | |
| echo "</pre>\n"; | |
| @ini_set('html_errors', $orig_html_errors); | |
| } | |
| evo_flush(); | |
| return true; | |
| } | |
| /** | |
| * Get a function trace from {@link debug_backtrace()} as html table. | |
| * | |
| * Adopted from {@link http://us2.php.net/manual/de/function.debug-backtrace.php#47644}. | |
| * | |
| * @todo dh> Add support for $is_cli = true (e.g. in case of MySQL error) | |
| * | |
| * @param integer|NULL Get the last x entries from the stack (after $ignore_from is applied). Anything non-numeric means "all". | |
| * @param array After a key/value pair matches a stack entry, this and the rest is ignored. | |
| * For example, array('class' => 'DB') would exclude everything after the stack | |
| * "enters" class DB and everything that got called afterwards. | |
| * You can also give an array of arrays which means that every condition in one of the given array must match. | |
| * @param integer Number of stack entries to include, after $ignore_from matches. | |
| * @return string HTML table | |
| */ | |
| function debug_get_backtrace( $limit_to_last = NULL, $ignore_from = array( 'function' => 'debug_get_backtrace' ), $offset_ignore_from = 0 ) | |
| { | |
| if( ! function_exists( 'debug_backtrace' ) ) // PHP 4.3.0 | |
| { | |
| return 'Function debug_backtrace() is not available!'; | |
| } | |
| $r = ''; | |
| $backtrace = debug_backtrace(); | |
| $count_ignored = 0; // remember how many have been ignored | |
| $limited = false; // remember if we have limited to $limit_to_last | |
| if( $ignore_from ) | |
| { // we want to ignore from a certain point | |
| $trace_length = 0; | |
| $break_because_of_offset = false; | |
| for( $i = count($backtrace); $i > 0; $i-- ) | |
| { // Search the backtrace from behind (first call). | |
| $l_stack = & $backtrace[$i-1]; | |
| if( $break_because_of_offset && $offset_ignore_from < 1 ) | |
| { // we've respected the offset, but need to break now | |
| break; // ignore from here | |
| } | |
| foreach( $ignore_from as $l_ignore_key => $l_ignore_value ) | |
| { // Check if we want to ignore from here | |
| if( is_array($l_ignore_value) ) | |
| { // It's an array - all must match | |
| foreach( $l_ignore_value as $l_ignore_mult_key => $l_ignore_mult_val ) | |
| { | |
| if( !isset($l_stack[$l_ignore_mult_key]) /* not set with this stack entry */ | |
| || strcasecmp($l_stack[$l_ignore_mult_key], $l_ignore_mult_val) /* not this value (case-insensitive) */ ) | |
| { | |
| continue 2; // next ignore setting, because not all match. | |
| } | |
| } | |
| if( $offset_ignore_from-- > 0 ) | |
| { | |
| $break_because_of_offset = true; | |
| break; | |
| } | |
| break 2; // ignore from here | |
| } | |
| elseif( isset($l_stack[$l_ignore_key]) | |
| && !strcasecmp($l_stack[$l_ignore_key], $l_ignore_value) /* is equal case-insensitive */ ) | |
| { | |
| if( $offset_ignore_from-- > 0 ) | |
| { | |
| $break_because_of_offset = true; | |
| break; | |
| } | |
| break 2; // ignore from here | |
| } | |
| } | |
| $trace_length++; | |
| } | |
| $count_ignored = count($backtrace) - $trace_length; | |
| $backtrace = array_slice( $backtrace, 0-$trace_length ); // cut off ignored ones | |
| } | |
| $count_backtrace = count($backtrace); | |
| if( is_numeric($limit_to_last) && $limit_to_last < $count_backtrace ) | |
| { // we want to limit to a maximum number | |
| $limited = true; | |
| $backtrace = array_slice( $backtrace, 0, $limit_to_last ); | |
| $count_backtrace = $limit_to_last; | |
| } | |
| $r .= '<div style="padding:1ex; margin-bottom:1ex; text-align:left; color:#000; background-color:#ddf;"> | |
| <h3>Backtrace:</h3>'."\n"; | |
| if( $count_backtrace ) | |
| { | |
| $r .= '<ol style="font-family:monospace;">'; | |
| $i = 0; | |
| foreach( $backtrace as $l_trace ) | |
| { | |
| if( ++$i == $count_backtrace ) | |
| { | |
| $r .= '<li style="padding:0.5ex 0;">'; | |
| } | |
| else | |
| { | |
| $r .= '<li style="padding:0.5ex 0; border-bottom:1px solid #77d;">'; | |
| } | |
| $args = array(); | |
| if( isset($l_trace['args']) && is_array( $l_trace['args'] ) ) | |
| { // Prepare args: | |
| foreach( $l_trace['args'] as $l_arg ) | |
| { | |
| $l_arg_type = gettype($l_arg); | |
| switch( $l_arg_type ) | |
| { | |
| case 'integer': | |
| case 'double': | |
| $args[] = $l_arg; | |
| break; | |
| case 'string': | |
| $args[] = '"'.strmaxlen(str_replace("\n", '\n', $l_arg), 255, NULL, 'htmlspecialchars').'"'; | |
| break; | |
| case 'array': | |
| $args[] = 'Array('.count($l_arg).')'; | |
| break; | |
| case 'object': | |
| $args[] = 'Object('.get_class($l_arg).')'; | |
| break; | |
| case 'resource': | |
| $args[] = htmlspecialchars((string)$l_arg); | |
| break; | |
| case 'boolean': | |
| $args[] = $l_arg ? 'true' : 'false'; | |
| break; | |
| default: | |
| $args[] = $l_arg_type; | |
| } | |
| } | |
| } | |
| $call = "<strong>\n"; | |
| if( isset($l_trace['class']) ) | |
| { | |
| $call .= htmlspecialchars($l_trace['class']); | |
| } | |
| if( isset($l_trace['type']) ) | |
| { | |
| $call .= htmlspecialchars($l_trace['type']); | |
| } | |
| $call .= htmlspecialchars($l_trace['function'])."( </strong>\n"; | |
| if( $args ) | |
| { | |
| $call .= ' '.implode( ', ', $args ).' '; | |
| } | |
| $call .='<strong>)</strong>'; | |
| $r .= $call."<br />\n"; | |
| $r .= '<strong>'; | |
| if( isset($l_trace['file']) ) | |
| { | |
| $r .= "File: </strong> ".$l_trace['file']; | |
| } | |
| else | |
| { | |
| $r .= '[runtime created function]</strong>'; | |
| } | |
| if( isset($l_trace['line']) ) | |
| { | |
| $r .= ' on line '.$l_trace['line']; | |
| } | |
| $r .= "</li>\n"; | |
| } | |
| $r .= '</ol>'; | |
| } | |
| else | |
| { | |
| $r .= '<p>No backtrace available.</p>'; | |
| } | |
| // Extra notes, might be to much, but explains why we stopped at some point. Feel free to comment it out or remove it. | |
| $notes = array(); | |
| if( $count_ignored ) | |
| { | |
| $notes[] = 'Ignored last: '.$count_ignored; | |
| } | |
| if( $limited ) | |
| { | |
| $notes[] = 'Limited to'.( $count_ignored ? ' remaining' : '' ).': '.$limit_to_last; | |
| } | |
| if( $notes ) | |
| { | |
| $r .= '<p class="small">'.implode( ' - ', $notes ).'</p>'; | |
| } | |
| $r .= "</div>\n"; | |
| return $r; | |
| } | |
| /** | |
| * Outputs Unexpected Error message. When in debug mode it also prints a backtrace. | |
| * | |
| * This should be used instead of die() everywhere. | |
| * This should NOT be used instead of exit() anywhere. | |
| * Dying means the application has encontered an unexpected situation, | |
| * i-e: something that should never occur during normal operation. | |
| * Examples: database broken, user changed URL by hand... | |
| * | |
| * @param string Message to output | |
| * @param array Additional params | |
| * - "status" (Default: '500 Internal Server Error') | |
| * - "debug_info" - Use this info instead of $additional_info when debug is ON | |
| */ | |
| function debug_die( $additional_info = '', $params = array() ) | |
| { | |
| global $debug, $baseurl; | |
| global $log_app_errors, $app_name, $is_cli, $display_errors_on_production, $is_api_request, $is_cron_job_executing; | |
| $params = array_merge( array( | |
| 'status' => '500 Internal Server Error', | |
| 'debug_info' => '', | |
| ), $params ); | |
| if( $debug && ! empty( $params['debug_info'] ) ) | |
| { // Display 'debug_info' when debug is ON | |
| $additional_info = $params['debug_info']; | |
| } | |
| // Send the predefined cookies: | |
| evo_sendcookies(); | |
| if( $is_api_request ) | |
| { // REST API or XMLRPC request: | |
| // Set JSON content type: | |
| headers_content_mightcache( 'application/json', 0, '#', false ); // Do NOT cache error messages! (Users would not see they fixed them) | |
| header_http_response( $params['status'] ); | |
| echo json_encode( array( | |
| 'error_status' => $params['status'], | |
| 'error_info' => $additional_info, | |
| ) ); | |
| die(1); // Error code 1. Note: This will still call the shutdown function. | |
| } | |
| elseif( $is_cron_job_executing ) | |
| { // If debug die has been called during cron job executing: | |
| $cron_job_error = '<div style="background-color: #ddd; padding: 1ex; margin-bottom: 1ex;">' | |
| .'<h3>Additional information about this error:</h3>' | |
| .$additional_info | |
| .'</div>' | |
| .debug_get_backtrace(); | |
| throw new Exception( str_replace( "\n", '', $cron_job_error ) ); | |
| } | |
| elseif( $is_cli ) | |
| { // Command line interface, e.g. in cron_exec.php: | |
| echo '== '.T_('An unexpected error has occurred!')." ==\n"; | |
| echo T_('If this error persists, please report it to the administrator.')."\n"; | |
| if( $debug || $display_errors_on_production ) | |
| { // Display additional info only in debug mode or when it was explicitly set by display_errors_on_production setting because it can reveal system info to hackers and greatly facilitate exploits | |
| echo T_('Additional information about this error:')."\n"; | |
| echo strip_tags( $additional_info )."\n\n"; | |
| } | |
| } | |
| else | |
| { | |
| // Attempt to output an error header (will not work if the output buffer has already flushed once): | |
| // This should help preventing indexing robots from indexing the error :P | |
| if( ! headers_sent() ) | |
| { | |
| load_funcs('_core/_template.funcs.php'); | |
| headers_content_mightcache( 'text/html', 0, '#', false ); // Do NOT cache error messages! (Users would not see they fixed them) | |
| $status_header = $_SERVER['SERVER_PROTOCOL'].' '.$params['status']; | |
| header($status_header); | |
| } | |
| $too_many_connections = false; | |
| if( ! empty( $additional_info ) ) | |
| { //Handling of "Too many connections": | |
| $find_token = 'Too many connections'; | |
| if( preg_match( "/{$find_token}/i", $additional_info ) ) | |
| { | |
| load_funcs( 'skins/_skin.funcs.php' ); | |
| $too_many_connections = true; | |
| require skin_fallback_path( 'too_many_connections.main.php', 6 ); | |
| http_response_code( 503 ); | |
| } | |
| } | |
| if( ! $too_many_connections ) | |
| { | |
| echo '<div style="background-color: #fdd; padding: 1ex; margin-bottom: 1ex;">'; | |
| echo '<h3 style="color:#f00;">'.T_('An unexpected error has occurred!').'</h3>'; | |
| echo '<p>'.T_('If this error persists, please report it to the administrator.').'</p>'; | |
| echo '<p><a href="'.$baseurl.'">'.T_('Go back to home page').'</a></p>'; | |
| echo '</div>'; | |
| if( ! empty( $additional_info ) ) | |
| { | |
| echo '<div style="background-color: #ddd; padding: 1ex; margin-bottom: 1ex;">'; | |
| if( $debug || $display_errors_on_production ) | |
| { // Display additional info only in debug mode or when it was explicitly set by display_errors_on_production setting because it can reveal system info to hackers and greatly facilitate exploits | |
| echo '<h3>'.T_('Additional information about this error:').'</h3>'; | |
| echo $additional_info; | |
| } | |
| else | |
| { | |
| echo '<p><i>Enable debugging to get additional information about this error.</i></p>' . get_manual_link('debugging','How to enable debug mode?'); | |
| } | |
| echo '</div>'; | |
| // Append the error text to AJAX log if it is AJAX request | |
| ajax_log_add( $additional_info, 'error' ); | |
| ajax_log_display(); | |
| } | |
| } | |
| } | |
| if( $log_app_errors > 1 || $debug ) | |
| { // Prepare backtrace | |
| $backtrace = debug_get_backtrace(); | |
| if( $log_app_errors > 1 || $is_cli ) | |
| { | |
| $backtrace_cli = trim(strip_tags($backtrace)); | |
| } | |
| } | |
| if( $log_app_errors ) | |
| { // Log error through PHP's logging facilities: | |
| $log_message = $app_name.' error: '; | |
| if( ! empty($additional_info) ) | |
| { | |
| $log_message .= trim( strip_tags($additional_info) ); | |
| } | |
| else | |
| { | |
| $log_message .= 'No info specified in debug_die()'; | |
| } | |
| // Get file and line info: | |
| $file = 'Unknown'; | |
| $line = 'Unknown'; | |
| if( function_exists('debug_backtrace') /* PHP 4.3 */ ) | |
| { // get the file and line | |
| foreach( debug_backtrace() as $v ) | |
| { | |
| if( isset($v['function']) && $v['function'] == 'debug_die' ) | |
| { | |
| $file = isset($v['file']) ? $v['file'] : 'Unknown'; | |
| $line = isset($v['line']) ? $v['line'] : 'Unknown'; | |
| break; | |
| } | |
| } | |
| } | |
| $log_message .= ' in '.$file.' at line '.$line; | |
| if( $log_app_errors > 1 ) | |
| { // Append backtrace: | |
| // indent after newlines: | |
| $backtrace_cli = preg_replace( '~(\S)(\n)(\S)~', '$1 $2$3', $backtrace_cli ); | |
| $log_message .= "\nBacktrace:\n".$backtrace_cli; | |
| } | |
| $log_message .= "\nREQUEST_URI: ".( isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '-' ); | |
| $log_message .= "\nHTTP_REFERER: ".( isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '-' ); | |
| error_log( str_replace("\n", ' / ', $log_message), 0 /* PHP's system logger */ ); | |
| } | |
| // DEBUG OUTPUT: | |
| if( $debug ) | |
| { | |
| if( $is_cli ) | |
| echo $backtrace_cli; | |
| else | |
| echo $backtrace; | |
| } | |
| // EXIT: | |
| if( ! $is_cli ) | |
| { // Attempt to keep the html valid (but it doesn't really matter anyway) | |
| echo '</body></html>'; | |
| } | |
| die(1); // Error code 1. Note: This will still call the shutdown function. | |
| } | |
| /** | |
| * Outputs Bad request Error message. When in debug mode it also prints a backtrace. | |
| * | |
| * This should be used when a bad user input is detected. | |
| * | |
| * @param string Message to output (HTML/TEXT) | |
| * @param string Output format: 'html', 'text' | |
| */ | |
| function bad_request_die( $additional_info = '', $error_code = '400 Bad Request', $format = 'html' ) | |
| { | |
| global $debug, $baseurl, $is_api_request; | |
| // Send the predefined cookies: | |
| evo_sendcookies(); | |
| if( $is_api_request ) | |
| { // REST API or XMLRPC request: | |
| // Set JSON content type: | |
| headers_content_mightcache( 'application/json', 0, '#', false ); // Do NOT cache error messages! (Users would not see they fixed them) | |
| header_http_response( $error_code ); | |
| echo json_encode( array( | |
| 'error_status' => $error_code, | |
| 'error_info' => $additional_info, | |
| ) ); | |
| die(2); // Error code 2. Note: this will still call the shutdown function. | |
| } | |
| // Attempt to output an error header (will not work if the output buffer has already flushed once): | |
| // This should help preventing indexing robots from indexing the error :P | |
| if( ! headers_sent() ) | |
| { | |
| load_funcs('_core/_template.funcs.php'); | |
| headers_content_mightcache( 'text/html', 0, '#', false ); // Do NOT cache error messages! (Users would not see they fixed them) | |
| header_http_response( $error_code ); | |
| } | |
| if( $format == 'text' ) | |
| { // Display error message in TEXT format: | |
| echo $additional_info; | |
| // Display AJAX Log if it was initialized: | |
| ajax_log_display(); | |
| die(2); // Error code 2. Note: this will still call the shutdown function. | |
| } | |
| // else display error message in HTML format: | |
| if( ! function_exists( 'T_' ) ) | |
| { // Load locale funcs to initialize function "T_" because it is used below: | |
| load_funcs( 'locales/_locale.funcs.php' ); | |
| } | |
| echo '<div style="background-color: #fdd; padding: 1ex; margin-bottom: 1ex;">'; | |
| echo '<h3 style="color:#f00;">'.T_('Bad Request!').'</h3>'; | |
| echo '<p>'.T_('The parameters of your request are invalid.').'</p>'; | |
| echo '<p>'.T_('If you have obtained this error by clicking on a link INSIDE of this site, please report the bad link to the administrator.').'</p>'; | |
| echo '<p><a href="'.$baseurl.'">'.T_('Go back to home page').'</a></p>'; | |
| echo '</div>'; | |
| if( !empty( $additional_info ) ) | |
| { | |
| echo '<div style="background-color: #ddd; padding: 1ex; margin-bottom: 1ex;">'; | |
| if( $debug ) | |
| { // Display additional info only in debug mode because it can reveal system info to hackers and greatly facilitate exploits | |
| echo '<h3>'.T_('Additional information about this error:').'</h3>'; | |
| echo $additional_info; | |
| } | |
| else | |
| { | |
| echo '<p><i>Enable debugging to get additional information about this error.</i></p>' . get_manual_link('debugging','How to enable debug mode?'); | |
| } | |
| echo '</div>'; | |
| // Append the error text to AJAX log if it is AJAX request | |
| ajax_log_add( $additional_info, 'error' ); | |
| } | |
| if( $debug ) | |
| { | |
| echo debug_get_backtrace(); | |
| } | |
| // Attempt to keep the html valid (but it doesn't really matter anyway) | |
| echo '</body></html>'; | |
| // Display AJAX Log if it was initialized: | |
| ajax_log_display(); | |
| die(2); // Error code 2. Note: this will still call the shutdown function. | |
| } | |
| /** | |
| * Outputs debug info, according to {@link $debug} or $force param. This gets called typically at the end of the page. | |
| * | |
| * @param boolean true to force output regardless of {@link $debug} | |
| * @param boolean true to force clean output (without HTML) regardless of {@link $is_cli} | |
| */ | |
| function debug_info( $force = false, $force_clean = false ) | |
| { | |
| global $debug, $debug_done, $debug_jslog, $debug_jslog_done, $Debuglog, $DB, $obhandler_debug, $Timer, $ReqHost, $ReqPath, $is_cli; | |
| global $cache_imgsize, $cache_File; | |
| global $Session; | |
| global $db_config, $tableprefix, $http_response_code, $disp, $disp_detail, $robots_index, $robots_follow, $content_type_header; | |
| /** | |
| * @var Hit | |
| */ | |
| global $Hit; | |
| // Detect content-type | |
| $content_type = NULL; | |
| foreach(headers_list() as $header) | |
| { | |
| if( stripos($header, 'content-type:') !== false ) | |
| { // content type sent | |
| # "Content-Type:text/html;charset=utf-8" => "text/html" | |
| $content_type = explode( ':', $header, 2 ); | |
| $content_type = explode( ';', array_pop( $content_type ) ); | |
| $content_type = trim( array_shift( $content_type ) ); | |
| break; | |
| } | |
| } | |
| // ---- Print AJAX Log | |
| if( empty( $debug_jslog_done ) && ( $debug || $debug_jslog ) && $content_type == 'text/html' ) | |
| { // Display debug jslog once | |
| global $rsc_url, $app_version_long; | |
| $relative_to = ( is_admin_page() ? 'rsc_url' : 'blog' ); | |
| require_js_defer( '#jqueryUI#', $relative_to, true ); | |
| require_css( '#jqueryUI_css#', $relative_to, NULL, NULL, '#', true ); | |
| require_js_defer( 'debug_jslog.js', $relative_to, true ); | |
| require_js_defer( 'ext:jquery/cookie/jquery.cookie.min.js', $relative_to, true ); | |
| $jslog_style_cookies = param_cookie( 'jslog_style', 'string' ); | |
| $jslog_styles = array(); | |
| if( !empty( $jslog_style_cookies ) ) | |
| { // Get styles only from cookies | |
| $jslog_style_cookies = explode( ';', $jslog_style_cookies ); | |
| foreach( $jslog_style_cookies as $jsc => $style ) | |
| { | |
| if( strpos( $style, 'height' ) !== false /*|| ( strpos( $style, 'display' ) !== false && !$debug_jslog )*/ ) | |
| { // Unset the height param from defined styles ( and the display param if jslog is disabled ) | |
| unset( $jslog_style_cookies[$jsc] ); | |
| } | |
| } | |
| $jslog_styles[] = implode( ';', $jslog_style_cookies ); | |
| } | |
| else | |
| { | |
| if( !is_logged_in() ) | |
| { // Align top when evobar is hidden | |
| $jslog_styles[] = 'top:0'; | |
| } | |
| if( $debug_jslog ) | |
| { // Display the jslog | |
| $jslog_styles[] = 'display:block'; | |
| } | |
| } | |
| $jslog_styles = count( $jslog_styles ) > 0 ? ' style="'.implode( ';', $jslog_styles ).'"' : ''; | |
| $close_url = url_add_param( $_SERVER['REQUEST_URI'], 'jslog' ); | |
| echo '<div id="debug_ajax_info" class="debug"'.$jslog_styles.'>'; | |
| echo '<div class="jslog_titlebar">'. | |
| 'AJAX Debug log'.get_manual_link('ajax_debug_log'). | |
| action_icon( T_('Close'), 'close', $close_url, NULL, NULL, NULL, array( 'class' => 'jslog_switcher' ) ). | |
| '</div>'; | |
| echo '<div id="jslog_container"></div>'; | |
| echo '<div class="jslog_statusbar">'. | |
| '<a href="'.$_SERVER['REQUEST_URI'].'#" class="jslog_clear">'.T_('Clear').'</a>'. | |
| '</div>'; | |
| echo '</div>'; | |
| // Make sure debug jslog output only happens once: | |
| $debug_jslog_done = true; | |
| } | |
| // ---- | |
| // clean output: | |
| $clean = $is_cli || $force_clean; | |
| if( ! $force ) | |
| { | |
| if( ! empty( $debug_done ) ) | |
| { // Already displayed! | |
| return; | |
| } | |
| if( empty( $debug ) || // No debug output desired: | |
| ( $debug < 2 && $content_type != 'text/html' ) ) // Do not display, if no content-type header has been sent or it's != "text/html" (debug > 1 skips this) | |
| { | |
| global $evo_last_handled_error; | |
| if( ! empty( $evo_last_handled_error ) ) | |
| { // If script has been stoppped by some error | |
| // Display a message when debug is OFF and error has occured | |
| $debug_off_title = 'An unexpected error has occured!'; | |
| $debug_off_msg1 = 'We apologize for the inconvenience.'; | |
| $debug_off_msg2 = 'This error has been automatically reported and we will work to resolve it as fast as possible.'; | |
| if( $clean ) | |
| { // CLI mode | |
| echo '*** '.$debug_off_title.' ***'."\n\n" | |
| .$debug_off_msg1."\n" | |
| .$debug_off_msg2; | |
| } | |
| else | |
| { // View from browser | |
| echo '<div style="margin:1em auto;padding:10px;background:#FEFFFF;border:2px solid #F00;border-radius:6px;text-align:center;">' | |
| .'<h2 style="margin:0;color:#F00;">'.$debug_off_title.'</h2>' | |
| .'<p>'.$debug_off_msg1.'</p>' | |
| .'<p style="margin-bottom:0">'.$debug_off_msg2.'</p>' | |
| .'</div>'; | |
| } | |
| } | |
| return; | |
| } | |
| } | |
| //Make sure debug output only happens once: | |
| $debug_done = true; | |
| $printf_format = '| %-45s | %-5s | %-7s | %-5s |'; | |
| $table_headerlen = 73; | |
| /* This calculates the number of dashes to print e. g. on the top and | |
| * bottom of the table and after the header, making the table look | |
| * better (looks like the tables of the mysql command line client). | |
| * Normally, the value won't change, so it's hardcoded above. If you | |
| * change the printf() format above, this might be useful. | |
| preg_match_all( '#\d+#', $printf_format, $table_headerlen ); | |
| $table_headerlen = array_sum( $table_headerlen[0] ) + | |
| strlen( preg_replace( '#[^ \|]+#', '', | |
| $printf_format ) ) - 2; | |
| */ | |
| $ReqHostPathQuery = $ReqHost.$ReqPath.( empty( $_SERVER['QUERY_STRING'] ) ? '' : '?'.$_SERVER['QUERY_STRING'] ); | |
| echo "\n\n\n"; | |
| if( ! $clean ) | |
| { | |
| echo '<div class="debug" id="debug_info">'; | |
| } | |
| // FULL DEBUG INFO(s) FROM PREVIOUS SESSION(s), after REDIRECT(s): | |
| $get_redirected_debuginfo_from_sess_ID = param( 'get_redirected_debuginfo_from_sess_ID', 'integer' ); | |
| if( ! empty( $get_redirected_debuginfo_from_sess_ID ) ) | |
| { // Get Session by ID for debug info from redirected page: | |
| // (This is used for redirect from different domain) | |
| $debug_info_Session = new Session( $get_redirected_debuginfo_from_sess_ID ); | |
| } | |
| elseif( isset( $Session ) ) | |
| { // Use current Session for debug info from redirected page: | |
| $debug_info_Session = $Session; | |
| } | |
| if( isset( $debug_info_Session ) && ! empty( $debug_info_Session->ID ) ) | |
| { // Get debug info from redirected page: | |
| $sess_debug_infos = $debug_info_Session->get( 'debug_infos' ); | |
| } | |
| if( ! empty( $sess_debug_infos ) ) | |
| { // Display debug info from redirected page: | |
| $count_sess_debug_infos = count( $sess_debug_infos ); | |
| if( $count_sess_debug_infos > 1 ) | |
| { // Links to those Debuglogs: | |
| if( $clean ) | |
| { // kind of useless, but anyway... | |
| echo "\n".'There are '.$count_sess_debug_infos.' debug infos from redirected pages.'."\n"; | |
| } | |
| else | |
| { | |
| echo '<p>There are '.$count_sess_debug_infos.' debug infos from redirected pages: '; | |
| for( $i = 1; $i <= $count_sess_debug_infos; $i++ ) | |
| { | |
| echo '<a href="'.$ReqHostPathQuery.'#debug_sess_debug_info_'.$i.'">#'.$i.'</a> '; | |
| } | |
| echo '</p>'; | |
| } | |
| } | |
| foreach( $sess_debug_infos as $k => $sess_debug_info ) | |
| { | |
| if( $clean ) | |
| { | |
| echo "\n".'== Debug messages from redirected page (#'.( $k + 1 ).') =='."\n" | |
| .'See below for the Debuglog from the current request.'."\n"; | |
| echo gzdecode( $sess_debug_info ); | |
| } | |
| else | |
| { | |
| echo '<div class="debug_session">'; | |
| echo '<h3 id="debug_sess_debug_info_'.( $k + 1 ).'" style="color:#f00">Debug messages from redirected page (#'.( $k + 1 ).')</h3>' | |
| // link to real Debuglog: | |
| .'<p><a href="'.$ReqHostPathQuery.'#debug_current">See below for the debug from the current request.</a></p>'; | |
| $sess_debug_info = gzdecode( $sess_debug_info ); | |
| // Fix all anchors to proper work with SESSION debug info and do NOT mix with current debug info where same achors are used but without number suffix ($k + 1): | |
| $anchors_regexp = '(evo_debug_queries|debug_info_cat_[^"]+)'; | |
| $sess_debug_info = preg_replace( '/id="'.$anchors_regexp.'"/', 'id="$1_'.( $k + 1 ).'"', $sess_debug_info ); | |
| echo preg_replace( '/href="([^#"]*)#'.$anchors_regexp.'"/', 'href="'.$ReqHostPathQuery.'#$2_'.( $k + 1 ).'"', $sess_debug_info ); | |
| echo '</div>'; | |
| } | |
| } | |
| if( ! $clean ) | |
| { // Anchor to scrolldown to current debug: | |
| echo '<a id="debug_current"></a>'; | |
| } | |
| // link to first sess_debug_infos: | |
| if( $clean ) | |
| { | |
| echo 'See above for the debug info(s) from before the redirect.'."\n"; | |
| } | |
| else | |
| { | |
| echo '<p><a href="'.$ReqHostPathQuery.'#debug_sess_debug_info_1">See above for the debug info(s) from before the redirect.</a></p>'; | |
| } | |
| // Delete logs since they have been displayed... | |
| // EXCEPT if we are redirecting, because in this case we won't see these logs in a browser (only in request debug tools) | |
| // So in that case we want them to move over to the next page... | |
| if( $http_response_code < 300 || $http_response_code >= 400 ) | |
| { // This is NOT a 3xx redirect, assume debuglogs have been seen & delete them: | |
| $debug_info_Session->delete( 'debug_infos' ); | |
| } | |
| echo "\n\n\n"; | |
| } | |
| // END OF DEBUG FROM PREVIOUS SESSIONS, after REDIRECT(s). | |
| $debug_info_title = empty( $count_sess_debug_infos ) ? 'Debug info' : 'Debug info from Current page'; | |
| echo ( $clean ? '*** '.$debug_info_title.' ***'."\n\n" : '<h2>'.$debug_info_title.'</h2>' ); | |
| if( !$obhandler_debug ) | |
| { // don't display changing items when we want to test obhandler | |
| // --------------------------- | |
| echo '<div class="log_container"><div>'; | |
| echo 'HTTP Response code: '.$http_response_code; | |
| echo $clean ? "\n" : '<br />'; | |
| echo '$content_type_header: '.$content_type_header; | |
| echo $clean ? "\n" : '<br />'; | |
| echo '$disp: '.$disp.' -- detail: '.$disp_detail; | |
| echo $clean ? "\n" : '<br />'; | |
| echo '$robots_index: '.$robots_index; | |
| echo $clean ? "\n" : '<br />'; | |
| echo '$robots_follow: '.$robots_follow; | |
| echo $clean ? "\n" : '<br />'; | |
| echo '</div></div>'; | |
| } | |
| if( !$obhandler_debug ) | |
| { // don't display changing items when we want to test obhandler | |
| // ================================== DB Summary ================================ | |
| if( isset($DB) ) | |
| { | |
| echo '<div class="log_container"><div>'; | |
| echo $DB->num_queries.' SQL queries executed in '.$Timer->get_duration( 'SQL QUERIES' )." seconds\n"; | |
| if( ! $clean ) | |
| { | |
| echo ' <a href="'.$ReqHostPathQuery.'#evo_debug_queries">scroll down to details</a><p>'; | |
| } | |
| echo '</div></div>'; | |
| } | |
| // ========================== Timer table ================================ | |
| $time_page = $Timer->get_duration( 'total' ); | |
| $timer_rows = array(); | |
| foreach( $Timer->get_categories() as $l_cat ) | |
| { | |
| if( $l_cat == 'sql_query' ) | |
| { | |
| continue; | |
| } | |
| $timer_rows[ $l_cat ] = $Timer->get_duration( $l_cat ); | |
| } | |
| // Don't sort to see orginal order of creation | |
| // arsort( $timer_rows ); | |
| // ksort( $timer_rows ); | |
| // Remove "total", it will get output as the last one: | |
| $total_time = $timer_rows['total']; | |
| unset($timer_rows['total']); | |
| $percent_total = $time_page > 0 ? number_format( 100/$time_page * $total_time, 2 ) : '0'; | |
| if( $clean ) | |
| { | |
| echo '== Timers =='."\n\n"; | |
| echo '+'.str_repeat( '-', $table_headerlen ).'+'."\n"; | |
| printf( $printf_format."\n", 'Category', 'Time', '%', 'Count' ); | |
| echo '+'.str_repeat( '-', $table_headerlen ).'+'."\n"; | |
| } | |
| else | |
| { | |
| echo '<table class="debug_timer"><thead>' | |
| .'<tr><td colspan="4" class="center">Timers</td></tr>' // dh> TODO: should be TH. Workaround so that tablesorter does not pick it up. Feedback from author requested. | |
| .'<tr><th>Category</th><th>Time</th><th>%</th><th>Count</th></tr>' | |
| .'</thead>'; | |
| // Output "total": | |
| echo "\n<tfoot><tr>" | |
| .'<td>total</td>' | |
| .'<td class="right red">'.$total_time.'</td>' | |
| .'<td class="right">'.$percent_total.'%</td>' | |
| .'<td class="right">'.$Timer->get_count('total').'</td></tr></tfoot>'; | |
| echo '<tbody>'; | |
| } | |
| $table_rows_collapse = array(); | |
| foreach( $timer_rows as $l_cat => $l_time ) | |
| { | |
| $percent_l_cat = $time_page > 0 ? number_format( 100/$time_page * $l_time, 2 ) : '0'; | |
| if( $clean ) | |
| { | |
| $row = sprintf( $printf_format, $l_cat, $l_time, $percent_l_cat.'%', $Timer->get_count( $l_cat ) ); | |
| } | |
| else | |
| { | |
| $row = "\n<tr>" | |
| .'<td>'.$l_cat.'</td>' | |
| .'<td class="right">'.$l_time.'</td>' | |
| .'<td class="right">'.$percent_l_cat.'%</td>' | |
| .'<td class="right">'.$Timer->get_count( $l_cat ).'</td></tr>'; | |
| } | |
| // Maybe ignore this row later, but not for clean display. | |
| if( ! $clean && ( $percent_l_cat < 1 ) ) | |
| { // Hide everything that tool less tahn 5% of the time | |
| $table_rows_collapse[] = $row; | |
| } | |
| else | |
| { | |
| echo $row."\n"; | |
| } | |
| } | |
| $count_collapse = count($table_rows_collapse); | |
| // Collapse ignored rows, allowing to expand them with Javascript: | |
| if( $count_collapse > 5 ) | |
| { | |
| echo '<tr><td colspan="4" class="center">'; | |
| echo '<a href="" onclick="jQuery(this).closest(\'tbody\').next().toggle(); return false;">+ '.$count_collapse.' queries < 1%</a> </td></tr>'; | |
| echo '</tbody>'; | |
| echo '<tbody style="display:none">'; | |
| } | |
| echo implode( "\n", $table_rows_collapse )."\n"; | |
| if ( $clean ) | |
| { // "total" (done in tfoot for html above) | |
| echo sprintf( $printf_format, 'total', $total_time, $percent_total.'%', $Timer->get_count('total') ); | |
| echo '+'.str_repeat( '-', $table_headerlen ).'+'."\n\n"; | |
| } | |
| else | |
| { | |
| echo "\n</tbody></table>"; | |
| // add jquery.tablesorter to the "Debug info" table. | |
| $relative_to = ( is_admin_page() ? 'rsc_url' : 'blog' ); | |
| require_js_defer( 'ext:jquery/tablesorter/jquery.tablesorter.min.js', $relative_to, true ); | |
| require_js_defer( 'src/evo_init_debug_timer.js', $relative_to, true ); | |
| } | |
| // ================================ Opcode caching ================================ | |
| echo '<div class="log_container"><div>'; | |
| echo 'Opcode cache: '.get_active_opcode_cache(); | |
| echo $clean ? "\n" : '<p>'; | |
| // ================================ User caching ================================ | |
| echo 'User cache: '.get_active_user_cache(); | |
| echo $clean ? "\n" : '<p>'; | |
| echo '</div></div>'; | |
| // ================================ Memory Usage ================================ | |
| echo '<div class="log_container"><div>'; | |
| foreach( array( // note: 8MB is default for memory_limit and is reported as 8388608 bytes | |
| 'memory_get_usage' => array( 'display' => 'Memory usage', 'high' => 8000000 ), | |
| 'memory_get_peak_usage' => array( 'display' => 'Memory peak usage', 'high' => 8000000 ) ) as $l_func => $l_var ) | |
| { | |
| if( function_exists( $l_func ) ) | |
| { | |
| $_usage = $l_func(); | |
| if( $_usage > $l_var['high'] ) | |
| { | |
| echo $clean ? '[!!] ' : '<span style="color:red; font-weight:bold">'; | |
| } | |
| echo $l_var['display'].': '.bytesreadable( $_usage, ! $clean ); | |
| if( ! $clean && $_usage > $l_var['high'] ) | |
| { | |
| echo '</span>'; | |
| } | |
| echo $clean ? "\n" : '<br />'; | |
| } | |
| } | |
| echo 'Len of serialized $cache_imgsize: '.strlen(serialize($cache_imgsize)); | |
| echo $clean ? "\n" : '<br />'; | |
| echo 'Len of serialized $cache_File: '.strlen(serialize($cache_File)); | |
| echo $clean ? "\n" : '<br />'; | |
| echo '</div></div>'; | |
| } | |
| // CURRENT DEBUGLOG (with list of categories at top): | |
| $log_categories = array( 'error', 'note', 'all' ); // Categories to output (in that order) | |
| $log_container_head = $clean ? ( "\n".'== Debug messages =='."\n" ) : '<h3 id="debug_debuglog">Debug messages</h3>'; | |
| if ( ! $clean ) | |
| { | |
| $log_cats = array_keys($Debuglog->get_messages( $log_categories )); // the real list (with all replaced and only existing ones) | |
| $log_head_links = array(); | |
| foreach( $log_cats as $l_cat ) | |
| { | |
| $log_head_links[] .= '<a href="'.$ReqHostPathQuery.'#debug_info_cat_'.str_replace( ' ', '_', $l_cat ).'">'.$l_cat.'</a>'; | |
| } | |
| $log_container_head .= implode( ' | ', $log_head_links ); | |
| echo format_to_output( | |
| $Debuglog->display( array( | |
| 'container' => array( 'string' => $log_container_head, 'template' => false ), | |
| 'all' => array( 'string' => '<h4 id="debug_info_cat_%s">%s:</h4>', 'template' => false ) ), | |
| '', false, $log_categories ), | |
| 'htmlbody' ); | |
| echo '<h3 id="evo_debug_queries">DB</h3>'; | |
| } | |
| else | |
| { | |
| echo format_to_output( | |
| $Debuglog->display( array( | |
| 'container' => array( 'string' => $log_container_head, 'template' => false ), | |
| 'all' => array( 'string' => '= %s ='."\n\n", 'template' => false ) ), | |
| '', false, $log_categories, '', 'raw', false ), | |
| 'raw' ); | |
| echo "\n".'== DB =='."\n\n"; | |
| } | |
| if($db_config) | |
| { | |
| if ( ! $clean ) | |
| { | |
| echo '<pre>'; | |
| } | |
| echo 'Config DB Username: '.$db_config['user']."\n". | |
| 'Config DB Database: '.$db_config['name']."\n". | |
| 'Config DB Host: '.(isset($db_config['host']) ? $db_config['host'] : 'unset (localhost)')."\n". | |
| 'Config DB tables prefix: '.$tableprefix."\n". | |
| 'Config DB connection charset: '.$db_config['connection_charset']."\n"; | |
| echo $clean ? "\n" : '</pre>'; | |
| } | |
| if( !isset($DB) ) | |
| { | |
| echo 'No DB object.'.( $clean ? "\n" : '' ); | |
| } | |
| else | |
| { | |
| echo '<pre>Current DB charset: '.$DB->get_connection_charset()."</pre>\n"; | |
| $DB->dump_queries( ! $clean ); | |
| } | |
| if ( ! $clean ) | |
| { | |
| echo '</div>'; | |
| } | |
| } | |
| /** | |
| * Exit when request is blocked | |
| * | |
| * @param string Block type: 'IP', 'Domain', 'Country' | |
| * @param string Log message | |
| * @param string Syslog origin type: 'core', 'plugin' | |
| * @param integer Syslog origin ID | |
| */ | |
| function exit_blocked_request( $block_type, $log_message, $syslog_origin_type = 'core', $syslog_origin_ID = NULL ) | |
| { | |
| global $debug; | |
| // Write system log for the request: | |
| syslog_insert( $log_message, 'warning', NULL, NULL, $syslog_origin_type, $syslog_origin_ID ); | |
| // Print out this text to inform an user: | |
| echo 'This request has been blocked.'; | |
| if( $debug ) | |
| { // Display additional info on debug mode: | |
| echo ' ('.$block_type.')'; | |
| } | |
| // EXIT: | |
| exit( 0 ); | |
| } | |
| /** | |
| * Check if the current request exceed the post max size limit. | |
| * If too much data was sent add an error message and call header redirect. | |
| */ | |
| function check_post_max_size_exceeded() | |
| { | |
| global $Messages; | |
| if( ( $_SERVER['REQUEST_METHOD'] == 'POST' ) && empty( $_POST ) && empty( $_FILES ) && ( $_SERVER['CONTENT_LENGTH'] > 0 ) ) | |
| { | |
| // Check post max size ini setting | |
| $post_max_size = ini_get( 'post_max_size' ); | |
| // Convert post_max_size value to bytes | |
| switch ( substr( $post_max_size, -1 ) ) | |
| { | |
| case 'G': | |
| $post_max_size = $post_max_size * 1024; | |
| case 'M': | |
| $post_max_size = $post_max_size * 1024; | |
| case 'K': | |
| $post_max_size = $post_max_size * 1024; | |
| } | |
| // Add error message and redirect back to the referer url | |
| $Messages->add( sprintf( T_('You have sent too much data (too many large files?) for the server to process (%s sent / %s maximum). Please try again by sending less data/files at a time.'), bytesreadable( $_SERVER['CONTENT_LENGTH'] ), bytesreadable( $post_max_size ) ) ); | |
| header_redirect( $_SERVER['HTTP_REFERER'] ); | |
| exit(0); // Already exited here | |
| } | |
| } | |
| /** | |
| * Prevent email header injection. | |
| */ | |
| function mail_sanitize_header_string( $header_str, $close_brace = false ) | |
| { | |
| // Prevent injection! (remove everything after (and including) \n or \r) | |
| $header_str = preg_replace( '~(\r|\n).*$~s', '', trim($header_str) ); | |
| if( $close_brace && strpos( $header_str, '<' ) !== false && strpos( $header_str, '>' ) === false ) | |
| { // We have probably stripped the '>' at the end! | |
| $header_str .= '>'; | |
| } | |
| return $header_str; | |
| } | |
| /** | |
| * Encode to RFC 1342 "Representation of Non-ASCII Text in Internet Message Headers" | |
| * | |
| * @param string | |
| * @param string 'Q' for Quoted printable, 'B' for base64 | |
| */ | |
| function mail_encode_header_string( $header_str, $mode = 'Q' ) | |
| { | |
| global $evo_charset; | |
| /* mbstring way (did not work for Alex RU) | |
| if( function_exists('mb_encode_mimeheader') ) | |
| { // encode subject | |
| $orig = mb_internal_encoding(); | |
| mb_internal_encoding('utf-8'); | |
| $r = mb_encode_mimeheader( $header_str, 'utf-8', $mode ); | |
| mb_internal_encoding($orig); | |
| return $r; | |
| } | |
| */ | |
| if( preg_match( '~[^a-z0-9!*+\-/ ]~i', $header_str ) ) | |
| { // If the string actually needs some encoding | |
| if( $mode == 'Q' ) | |
| { // Quoted printable is best for reading with old/text terminal mail reading/debugging stuff: | |
| $header_str = preg_replace_callback( '#[^a-z0-9!*+\-/ ]#i', 'mail_encode_header_string_callback', $header_str ); | |
| $header_str = str_replace( ' ', '_', $header_str ); | |
| $header_str = '=?'.$evo_charset.'?Q?'.$header_str.'?='; | |
| } | |
| else | |
| { // Base 64 -- Alex RU way: | |
| $header_str = '=?'.$evo_charset.'?B?'.base64_encode( $header_str ).'?='; | |
| } | |
| } | |
| return $header_str; | |
| } | |
| /** | |
| * Callback function for mail header encoding | |
| * | |
| * @param array Matches | |
| * @return string | |
| */ | |
| function mail_encode_header_string_callback( $matches ) | |
| { | |
| return sprintf( '=%02x', ord( stripslashes( $matches[0] ) ) ); | |
| } | |
| /** | |
| * Get setting's value from General or User's settings | |
| * | |
| * @param integer User ID | |
| * @param string Setting ( email | name ) | |
| * @return string Setting's value | |
| */ | |
| function user_get_notification_sender( $user_ID, $setting ) | |
| { | |
| global $Settings; | |
| $setting_name = 'notification_sender_'.$setting; | |
| if( empty( $user_ID ) ) | |
| { // Get value from general settings | |
| return $Settings->get( $setting_name ); | |
| } | |
| $UserCache = & get_UserCache(); | |
| if( $User = & $UserCache->get_by_ID( $user_ID ) ) | |
| { | |
| if( $User->check_status( 'is_validated' ) ) | |
| { // User is Activated or Autoactivated or Manually activated | |
| global $UserSettings; | |
| if( $UserSettings->get( $setting_name, $user_ID ) == '' ) | |
| { // The user's setting is not defined yet | |
| // Update the user's setting from general setting | |
| $UserSettings->set( $setting_name, $Settings->get( $setting_name ), $user_ID ); | |
| $UserSettings->dbupdate(); | |
| } | |
| else | |
| { // User has a defined setting; Use this | |
| return $UserSettings->get( $setting_name, $user_ID ); | |
| } | |
| } | |
| } | |
| return $Settings->get( $setting_name ); | |
| } | |
| /** | |
| * Get limit for email sending for currently executing cron job | |
| * | |
| * @return integer 0 means either unlimited email sending or no cron job is executing at the moment | |
| */ | |
| function get_cron_job_emails_limit() | |
| { | |
| global $executing_cron_task_key; | |
| if( isset( $executing_cron_task_key ) ) | |
| { // If any cron job is really executing now: | |
| global $Settings; | |
| return intval( $Settings->get( 'cjob_maxemail_'.$executing_cron_task_key ) ); | |
| } | |
| // No currently executing cron job: | |
| return 0; | |
| } | |
| /** | |
| * Check if current executing cron job should be stopped because of email sending limit | |
| * | |
| * 1. THIS MUST BE CALLED ONLY FROM INSIDE THE MAIN LOOP of the cronjob (*.job.php), not from within a generic function. | |
| * 2. THE GOAL is to NOT overload the sending mail server by creating a PAUSE afer (approximately) the pax number of email to send in a chunk | |
| * 2B. The goal is that the next execution of the cron job will continue sending emails where the current job has left off. | |
| * 2C. The goal is NOT to miss sending notifications by generating send errors! | |
| * 3. Counter increase must be done SEPARATELY when an email is sent. | |
| * | |
| * @return boolean TRUE if cron job execution can continue, FALSE - when max emails was reached | |
| */ | |
| function check_cron_job_emails_limit() | |
| { | |
| // Get max number of emails for current cron job: | |
| $current_cron_job_emails_limit = get_cron_job_emails_limit(); | |
| if( $current_cron_job_emails_limit > 0 ) | |
| { // If current cron job has a limit for sending of emails: | |
| global $executing_cron_task_emails_count; | |
| if( $executing_cron_task_emails_count >= $current_cron_job_emails_limit ) | |
| { // The limit was reached: | |
| global $executing_cron_task_emails_reached, $mail_log_message; | |
| $mail_log_message = 'Stopping execution because max number of emails ('.$current_cron_job_emails_limit.') has been sent.'; | |
| if( empty( $executing_cron_task_emails_reached ) ) | |
| { // Append warning to cron log only if max emails is not reached yet: | |
| cron_log_append( $mail_log_message."\n", 'warning' ); | |
| // Set flag to don't show the above warning twice: | |
| $executing_cron_task_emails_reached = true; | |
| } | |
| // Stop current job execution: | |
| return false; | |
| } | |
| } | |
| // Allow to continue current cron job execution: | |
| return true; | |
| } | |
| /** | |
| * Get additional error message on failed mail sending | |
| * | |
| * @return string | |
| */ | |
| function get_send_mail_error() | |
| { | |
| global $Settings; | |
| if( $Settings->get( 'email_service' ) == 'smtp' && ! $Settings->get( 'force_email_sending' ) ) | |
| { // Only SMTP is used: | |
| return T_('Recipient email address does not seem to work.'); | |
| } | |
| else | |
| { // PHP "mail" function is used by default or it was forced after failed SMTP sending: | |
| return T_('Possible reason: the PHP mail() function may have been disabled on the server.'); | |
| } | |
| } | |
| /** | |
| * Sends an email, wrapping PHP's mail() function. | |
| * ALL emails sent by b2evolution must be sent through this function (for consistency and for logging) | |
| * | |
| * {@link $current_locale} will be used to set the charset. | |
| * | |
| * Note: we use a single \n as line ending, though it does not comply to {@link http://www.faqs.org/rfcs/rfc2822 RFC2822}, but seems to be safer, | |
| * because some mail transfer agents replace \n by \r\n automatically. | |
| * | |
| * @todo Unit testing with "nice addresses" This gets broken over and over again. | |
| * | |
| * @param string Recipient email address. | |
| * @param string Recipient name. | |
| * @param string Subject of the mail | |
| * @param string|array The message text OR Array: 'charset', 'full', 'html', 'text' | |
| * @param string From address, being added to headers (we'll prevent injections); see {@link http://securephp.damonkohler.com/index.php/Email_Injection}. | |
| * Defaults to {@link GeneralSettings::get('notification_sender_email') } if NULL. | |
| * @param string From name. | |
| * @param array Additional headers ( headername => value ). Take care of injection! | |
| * @param integer User ID | |
| * @param integer Email Campaign ID | |
| * @param integer Automation ID | |
| * @return boolean True if mail could be sent (not necessarily delivered!), false if not - (return value of {@link mail()}) | |
| */ | |
| function send_mail( $to, $to_name, $subject, $message, $from = NULL, $from_name = NULL, $headers = array(), $user_ID = NULL, $email_campaign_ID = NULL, $automation_ID = NULL ) | |
| { | |
| global $servertimenow, $email_send_simulate_only, $email_send_allow_php_mail; | |
| /** | |
| * @var string|NULL This global var stores a last mail log message | |
| */ | |
| global $mail_log_message; | |
| $mail_log_message = NULL; | |
| global $debug, $app_name, $app_version, $current_locale, $current_charset, $evo_charset, $locales, $Debuglog, $Settings, $demo_mode, $mail_log_insert_ID; | |
| $message_data = $message; | |
| if( is_array( $message_data ) && isset( $message_data['full'] ) ) | |
| { // If content is multipart | |
| $message = $message_data['full']; | |
| } | |
| elseif( is_string( $message_data ) ) | |
| { // Convert $message_data to array | |
| $message_data = array( 'full' => $message ); | |
| } | |
| // Replace secret content in the mail logs message body | |
| $message = preg_replace( '~\$secret_content_start\$.*\$secret_content_end\$~', '***secret-content-removed***', $message ); | |
| // Remove secret content marks from the message | |
| $message_data = str_replace( array( '$secret_content_start$', '$secret_content_end$' ), '', $message_data ); | |
| // Memorize email address | |
| $to_email_address = $to; | |
| $NL = "\r\n"; | |
| if( $demo_mode ) | |
| { // Debug mode restriction: Sending email in demo mode is not allowed | |
| return false; | |
| } | |
| if( !is_array( $headers ) ) | |
| { // Make sure $headers is an array | |
| $headers = array( $headers ); | |
| } | |
| if( empty( $from ) ) | |
| { | |
| $from = user_get_notification_sender( $user_ID, 'email' ); | |
| } | |
| if( empty( $from_name ) ) | |
| { | |
| $from_name = user_get_notification_sender( $user_ID, 'name' ); | |
| } | |
| // Pass these data for SMTP mailer | |
| $message_data['to_email'] = $to; | |
| $message_data['to_name'] = empty( $to_name ) ? NULL : $to_name; | |
| $message_data['from_email'] = $from; | |
| $message_data['from_name'] = empty( $from_name ) ? NULL : $from_name; | |
| $return_path = $Settings->get( 'notification_return_path' ); | |
| // Add real name into $from... | |
| if( ! is_windows() ) | |
| { // fplanque: Windows XP, Apache 1.3, PHP 4.4, MS SMTP : will not accept "nice" addresses. | |
| if( !empty( $to_name ) ) | |
| { | |
| $to = '"'.mail_encode_header_string($to_name).'" <'.$to.'>'; | |
| } | |
| if( !empty( $from_name ) ) | |
| { | |
| $from = '"'.mail_encode_header_string($from_name).'" <'.$from.'>'; | |
| } | |
| } | |
| $from = mail_sanitize_header_string( $from, true ); | |
| // From has to go into headers | |
| $headers['From'] = $from; | |
| if( !empty( $return_path ) ) | |
| { // Set a return path | |
| $headers['Return-Path'] = $return_path; | |
| } | |
| // echo 'sending email to: ['.htmlspecialchars($to).'] from ['.htmlspecialchars($from).']'; | |
| $clear_subject = $subject; | |
| $subject = mail_encode_header_string($subject); | |
| $message = str_replace( array( "\r\n", "\r" ), $NL, $message ); | |
| if( !isset( $headers['Content-Type'] ) ) | |
| { // Specify charset and content-type of email | |
| $headers['Content-Type'] = 'text/plain; charset='.$current_charset; | |
| } | |
| $headers['MIME-Version'] = '1.0'; | |
| $headers['Date'] = gmdate( 'r', $servertimenow ); | |
| // ADDITIONAL HEADERS: | |
| $headers['X-Mailer'] = $app_name.' '.$app_version.' - PHP/'.phpversion(); | |
| $ip_list = implode( ',', get_ip_list() ); | |
| if( !empty( $ip_list ) ) | |
| { // Add X-Remote_Addr param only if its value is not empty | |
| $headers['X-Remote-Addr'] = $ip_list; | |
| } | |
| // COMPACT HEADERS: | |
| $headerstring = get_mail_headers( $headers, $NL ); | |
| // Create initial email log with empty message | |
| $email_key = generate_random_key(); | |
| mail_log( $user_ID, $to_email_address, $clear_subject, NULL, $headerstring, 'ready_to_send', $email_key, $email_campaign_ID, $automation_ID ); | |
| // Replace tracking code placeholders | |
| $message = str_replace( array( '$email_key$', '$mail_log_ID$' ), array( $email_key, $mail_log_insert_ID ), $message ); | |
| $message_data = str_replace( array( '$email_key$', '$mail_log_ID$' ), array( $email_key, $mail_log_insert_ID ), $message_data ); | |
| // Set an additional parameter for the return path: | |
| switch( $Settings->get( 'sendmail_params' ) ) | |
| { | |
| case 'return': | |
| $sendmail_params = '-r $return-address$'; | |
| break; | |
| case 'from': | |
| $sendmail_params = '-f $return-address$'; | |
| break; | |
| case 'custom': | |
| $sendmail_params = $Settings->get( 'sendmail_params_custom' ); | |
| break; | |
| } | |
| if( ! empty( $sendmail_params ) ) | |
| { | |
| $additional_parameters = str_replace( | |
| array( '$from-address$', '$return-address$' ), | |
| array( $message_data['from_email'], ( empty( $return_path ) ? $message_data['from_email'] : $return_path ) ), | |
| $sendmail_params ); | |
| } | |
| else | |
| { | |
| $additional_parameters = ''; | |
| } | |
| // Remove email key markers from message that will be sent to actual email | |
| $message_data = str_replace( array( '$email_key_start$', '$email_key_end$' ), '', $message_data ); | |
| if( mail_is_blocked( $to_email_address ) ) | |
| { // Check if the email address is blocked | |
| $mail_log_message = 'Sending mail to "'.$to_email_address.'" FAILED, because this email is marked with spam or permanent errors.'; | |
| $Debuglog->add( htmlspecialchars( $mail_log_message ), 'error' ); | |
| update_mail_log( $mail_log_insert_ID, 'blocked', $message ); | |
| return false; | |
| } | |
| // Simulate email sending when: | |
| // - this is forced by config | |
| // - php mail sending is disabled by config and SMTP sending is not enabled | |
| $simulate_email_sending = $email_send_simulate_only || ( ! $email_send_allow_php_mail && ! $Settings->get( 'smtp_enabled' ) ); | |
| if( $simulate_email_sending ) | |
| { // The email sending is turned on simulation mode, Don't send a real message: | |
| $send_mail_result = true; | |
| } | |
| else | |
| { // Send email message on real mode: | |
| try | |
| { // Try to send: | |
| $send_mail_result = evo_mail( $to, $subject, $message_data, $headers, $additional_parameters ); | |
| } | |
| catch( Exception $ex ) | |
| { // Unexpected error: | |
| $send_mail_result = false; | |
| // Log the caught error: | |
| $mail_log_message = $ex->getMessage(); | |
| // Insert a returned email's data into DB: | |
| load_funcs( 'cron/model/_decode_returned_emails.funcs.php' ); | |
| $content = dre_limit_by_terminators( $mail_log_message ); | |
| $email_data = dre_get_email_data( $content, $mail_log_message, 'Empty headers' ); | |
| if( empty( $email_data['address'] ) ) | |
| { // Use current email address if no email address is detected in the error message: | |
| $email_data['address'] = $to_email_address; | |
| } | |
| dre_insert_returned_email( $email_data ); | |
| } | |
| } | |
| if( get_cron_job_emails_limit() > 0 ) | |
| { // Increase global counter if currently executing cron jobs has a limit for mail sending: | |
| global $executing_cron_task_emails_count; | |
| $executing_cron_task_emails_count++; | |
| } | |
| if( ! $send_mail_result ) | |
| { // The message has not been sent successfully | |
| $mail_log_message = 'Sending mail from "'.$from.'" to "'.$to.'", Subject "'.$subject.'" FAILED'.( $mail_log_message === NULL ? '' : ', Error: '.$mail_log_message ).'.'; | |
| update_mail_log( $mail_log_insert_ID, 'error', $message ); | |
| if( $debug > 1 ) | |
| { // We agree to die for debugging... | |
| debug_die( htmlspecialchars( $mail_log_message ) ); | |
| } | |
| else | |
| { // Soft debugging only.... | |
| $Debuglog->add( htmlspecialchars( $mail_log_message ), 'error' ); | |
| return false; | |
| } | |
| } | |
| $mail_log_message = 'Sent mail from "'.$from.'" to "'.$to.'", Subject "'.$subject.'".'; | |
| $Debuglog->add( htmlspecialchars( $mail_log_message ) ); | |
| update_mail_log( $mail_log_insert_ID, ( $simulate_email_sending ? 'simulated' : 'ok' ), $message ); | |
| return true; | |
| } | |
| /** | |
| * Sends an email to User | |
| * | |
| * @param integer Recipient ID. | |
| * @param string Subject of the mail | |
| * @param string Email template name | |
| * @param array Email template params | |
| * @param boolean Force to send this email even if the user is not activated. By default not activated user won't get emails. | |
| * Pasword reset, and account activation emails must be always forced. | |
| * @param array Additional headers ( headername => value ). Take care of injection! | |
| * @param string Use this param if you want use different email address instead of $User->email | |
| * @return boolean True if mail could be sent (not necessarily delivered!), false if not - (return value of {@link mail()}) | |
| */ | |
| function send_mail_to_User( $user_ID, $subject, $template_name, $template_params = array(), $force_on_non_activated = false, $headers = array(), $force_email_address = '' ) | |
| { | |
| global $UserSettings, $Settings, $current_charset; | |
| /** | |
| * @var string|NULL This global var stores a last mail log message | |
| */ | |
| global $mail_log_message; | |
| $mail_log_message = NULL; | |
| $UserCache = & get_UserCache(); | |
| if( $User = $UserCache->get_by_ID( $user_ID ) ) | |
| { | |
| if( !$User->check_status( 'can_receive_any_message' ) ) | |
| { // user status doesn't allow to receive nor emails nor private messages | |
| $mail_log_message = 'Sending mail to User #'.$User->ID.'('.$User->get( 'login' ).') is FAILED, because user status "'.$User->get( 'status' ).'" doesn\'t allow to receive any message.'; | |
| return false; | |
| } | |
| if( !( $User->check_status( 'is_validated' ) || $force_on_non_activated ) ) | |
| { // user is not activated and non activated users should not receive emails, unless force_on_non_activated is turned on | |
| $mail_log_message = 'Sending mail to User #'.$User->ID.'('.$User->get( 'login' ).') is FAILED, because user is not validated with status "'.$User->get( 'status' ).'".'; | |
| return false; | |
| } | |
| // Check if a new email to User with the corrensponding email type is allowed | |
| switch( $template_name ) | |
| { | |
| case 'account_activate': | |
| case 'account_delete_warning': | |
| if( $Settings->get( 'validation_process' ) == 'easy' && !$template_params['is_reminder'] ) | |
| { // this is not a notification email | |
| break; | |
| } | |
| // Check a day notification limit for user settings "Notify me by email whenever": | |
| case 'private_message_new': | |
| // 'notify_messages' - "I receive a private message." | |
| case 'private_messages_unread_reminder': | |
| // 'notify_unread_messages' - "I have unread private messages for more than X seconds."(X = $Settings->get( 'unread_message_reminder_threshold' )) | |
| case 'comment_new': | |
| // 'notify_comment_mentioned' - "I have been mentioned on a comment.", | |
| // 'notify_published_comments' - "a comment is published on one of my posts.", | |
| // 'notify_comment_moderation' - "a comment is posted and I have permissions to moderate it.", | |
| // 'notify_edit_cmt_moderation' - "a comment is modified and I have permissions to moderate it.", | |
| // 'notify_meta_comment_mentioned' - "I have been mentioned on an internal comment.", | |
| // 'notify_meta_comments' - "an internal comment is posted.". | |
| case 'comment_spam': | |
| // 'notify_spam_cmt_moderation' - "a comment is reported as spam and I have permissions to moderate it." | |
| case 'comments_unmoderated_reminder': | |
| // 'send_cmt_moderation_reminder' - "comments are awaiting moderation for more than X seconds."(X = $Settings->get( 'comment_moderation_reminder_threshold' )) | |
| case 'post_new': | |
| // 'notify_post_mentioned' - "I have been mentioned on a post.", | |
| // 'notify_post_moderation' - "a post is created and I have permissions to moderate it." | |
| // 'notify_edit_pst_moderation' - "someone proposed a change on a post and I have permissions to moderate it." | |
| case 'post_proposed_change': | |
| // 'notify_post_proposed' - "someone proposed a change on a post and I have permissions to moderate it." | |
| case 'post_assignment': | |
| // 'notify_post_assignment' - "a post was assigned to me." | |
| case 'posts_unmoderated_reminder': | |
| // 'send_pst_moderation_reminder' - "posts are awaiting moderation for more than X seconds."(X = $Settings->get( 'post_moderation_reminder_threshold' )) | |
| case 'posts_stale_alert': | |
| // 'send_pst_stale_alert' - "there are stale posts and I have permission to moderate them." | |
| case 'account_activate': | |
| case 'account_delete_warning': | |
| // 'send_activation_reminder' - "my account was deactivated or is not activated for more than X seconds."(X - $Settings->get( 'activate_account_reminder_threshold' )) | |
| case 'account_inactive': | |
| // 'send_inactive_reminder' - "my account has been inactive for more than X months."(X - $Settings->get( 'inactive_account_reminder_threshold' )) | |
| case 'account_new': | |
| // 'notify_new_user_registration' - "a new user has registered." | |
| case 'account_activated': | |
| // 'notify_activated_account' - "an account was activated." | |
| case 'account_closed': | |
| // 'notify_closed_account' - "an account was closed." | |
| case 'account_reported': | |
| // 'notify_reported_account' - "an account was reported." | |
| case 'account_changed': | |
| // 'notify_changed_account' - "an account was changed." | |
| case 'scheduled_task_error_report': | |
| // 'notify_cronjob_error' - "a scheduled task ends with an error or timeout." | |
| case 'list_new_subscriber': | |
| // 'notify_list_new_subscriber' - "one of my Lists gets a new subscriber." | |
| case 'list_lost_subscriber': | |
| // 'notify_list_lost_subscriber' - "one of my Lists loses a subscriber." | |
| case 'automation_owner_notification': | |
| // 'notify_automation_owner' - "one of my automations wants to notify me." | |
| $email_limit_setting = 'notification_email_limit'; | |
| $email_counter_setting = 'last_notification_email'; | |
| if( !check_allow_new_email( $email_limit_setting, $email_counter_setting, $User->ID ) ) | |
| { // more notification email is not allowed today | |
| $mail_log_message = 'Sending mail to User #'.$User->ID.'('.$User->get( 'login' ).') is FAILED, because user is already limited to receive more notifications for TODAY.'; | |
| return false; | |
| } | |
| break; | |
| case 'newsletter': | |
| // this is a newsletter email | |
| $email_limit_setting = 'newsletter_limit'; | |
| $email_counter_setting = 'last_newsletter'; | |
| if( !check_allow_new_email( $email_limit_setting, $email_counter_setting, $User->ID ) ) | |
| { // more newsletter email is not allowed today | |
| $mail_log_message = 'Sending mail to User #'.$User->ID.'('.$User->get( 'login' ).') is FAILED, because user is already limited to receive more newsletters for TODAY.'; | |
| return false; | |
| } | |
| break; | |
| case 'newsletter_test': | |
| // this is a newsletter email, used to send test email by current admin | |
| $template_name = 'newsletter'; | |
| break; | |
| } | |
| // Update notification sender's info from General settings | |
| $User->update_sender(); | |
| switch( $UserSettings->get( 'email_format', $User->ID ) ) | |
| { // Set Content-Type from user's setting "Email format" | |
| case 'auto': | |
| $template_params['boundary'] = 'b2evo-'.md5( rand() ); | |
| $headers['Content-Type'] = 'multipart/mixed; boundary="'.$template_params['boundary'].'"'; | |
| break; | |
| case 'html': | |
| $headers['Content-Type'] = 'text/html; charset='.$current_charset; | |
| break; | |
| case 'text': | |
| $headers['Content-Type'] = 'text/plain; charset='.$current_charset; | |
| break; | |
| } | |
| if( ! isset( $template_params['recipient_User'] ) ) | |
| { // Set recipient User, it should be defined for each template because of email footer | |
| $template_params['recipient_User'] = $User; | |
| } | |
| // Pass all email headers to template because they may be used there, e.g. in email footer template: | |
| $template_params['email_headers'] = $headers; | |
| // Get a message text from template file | |
| $message = mail_template( $template_name, $UserSettings->get( 'email_format', $User->ID ), $template_params, $User ); | |
| // Autoinsert user's data | |
| $subject = mail_autoinsert_user_data( $subject, $User, 'text', NULL, NULL, $template_params ); | |
| // erhsatingin > moved to mail_template() | |
| //$message = mail_autoinsert_user_data( $message, $User ); | |
| $to_email = !empty( $force_email_address ) ? $force_email_address : $User->email; | |
| // Params for email log: | |
| $email_campaign_ID = empty( $template_params['ecmp_ID'] ) ? NULL : $template_params['ecmp_ID']; | |
| $automation_ID = empty( $template_params['autm_ID'] ) ? NULL : $template_params['autm_ID']; | |
| if( send_mail( $to_email, NULL, $subject, $message, NULL, NULL, $headers, $user_ID, $email_campaign_ID, $automation_ID ) ) | |
| { // email was sent, update last email settings; | |
| if( isset( $email_limit_setting, $email_counter_setting ) ) | |
| { // User Settings(email counters) need to be updated | |
| update_user_email_counter( $email_limit_setting, $email_counter_setting, $user_ID ); | |
| } | |
| return true; | |
| } | |
| } | |
| // No user or email could not be sent | |
| return false; | |
| } | |
| /** | |
| * Sends an email to anonymous User | |
| * | |
| * @param integer Recipient ID. | |
| * @param string Subject of the mail | |
| * @param string Email template name | |
| * @param array Email template params | |
| * @param boolean Force to send this email even if the user is not activated. By default not activated user won't get emails. | |
| * Pasword reset, and account activation emails must be always forced. | |
| * @param array Additional headers ( headername => value ). Take care of injection! | |
| * @param string Use this param if you want use different email address instead of $User->email | |
| * @return boolean True if mail could be sent (not necessarily delivered!), false if not - (return value of {@link mail()}) | |
| */ | |
| function send_mail_to_anonymous_user( $user_email, $user_name, $subject, $template_name, $template_params = array(), $force_on_non_activated = false, $headers = array(), $force_email_address = '' ) | |
| { | |
| /** | |
| * @var string|NULL This global var stores a last mail log message | |
| */ | |
| global $mail_log_message; | |
| $mail_log_message = NULL; | |
| // Check if a new email to anonymous user with the corrensponding email type is allowed: | |
| switch( $template_name ) | |
| { | |
| case 'comment_new': | |
| // Notify anonymous user of replies: | |
| if( isset( $template_params['comment_ID'] ) && ! check_allow_new_anon_email( $template_params['comment_ID'] ) ) | |
| { // more notification email is not allowed today: | |
| $mail_log_message = 'Sending mail to anonymous user #'.$user_email.'('.$user_name.') is FAILED, because user is already limited to receive more notifications for TODAY.'; | |
| return false; | |
| } | |
| break; | |
| } | |
| $template_params['boundary'] = 'b2evo-'.md5( rand() ); | |
| $headers['Content-Type'] = 'multipart/mixed; boundary="'.$template_params['boundary'].'"'; | |
| if( ! isset( $template_params['anonymous_recipient_name'] ) ) | |
| { // Set recipient User, it should be defined for each template because of email footer: | |
| $template_params['anonymous_recipient_name'] = $user_name; | |
| } | |
| // Get a message text from template file: | |
| $message = mail_template( $template_name, 'auto', $template_params ); | |
| // Autoinsert user's data: | |
| $subject = mail_autoinsert_user_data( $subject, NULL, 'text', $user_email, $user_name, $template_params ); | |
| // Params for email log: | |
| $email_campaign_ID = empty( $template_params['ecmp_ID'] ) ? NULL : $template_params['ecmp_ID']; | |
| $automation_ID = empty( $template_params['autm_ID'] ) ? NULL : $template_params['autm_ID']; | |
| if( send_mail( $user_email, $user_name, $subject, $message, NULL, NULL, $headers, NULL, $email_campaign_ID, $automation_ID ) ) | |
| { // Email was sent: | |
| if( isset( $template_params['comment_ID'] ) ) | |
| { // Anonymous user settings(email counters) need to be updated: | |
| update_anon_user_email_counter( $template_params['comment_ID'] ); | |
| } | |
| return true; | |
| } | |
| // No user or email could not be sent: | |
| return false; | |
| } | |
| /** | |
| * Autoinsert user's data into subject or message of the email | |
| * | |
| * @param string Text | |
| * @param object User | |
| * @param string Format: 'html', 'text' | |
| * @param string Email of anonymous user | |
| * @param string Name of anonymous user | |
| * @param array Email template params | |
| * @return string Text | |
| */ | |
| function mail_autoinsert_user_data( $text, $User = NULL, $format = 'text', $user_email = NULL, $user_name = NULL, $params = array() ) | |
| { | |
| if( ! $User && ! ( $user_email || $user_email ) ) | |
| { // No user data: | |
| return $text; | |
| } | |
| if( $User ) | |
| { // Get data of registered User: | |
| global $UserSettings; | |
| if( $format == 'html' ) | |
| { | |
| $username = $User->get_colored_login( array( | |
| 'mask' => '$avatar$ $login$', | |
| 'login_text' => 'name', | |
| 'use_style' => true, | |
| 'extra_class' => 'normal_weight', | |
| 'protocol' => 'http:', | |
| ) ); | |
| $user_login = $User->get_colored_login( array( | |
| 'mask' => '$avatar$ $login$', | |
| 'use_style' => true, | |
| 'protocol' => 'http:', | |
| ) ); | |
| } | |
| else | |
| { | |
| $username = $User->get_username(); | |
| $user_login = $User->login; | |
| } | |
| $firstname = $User->get( 'firstname' ); | |
| $lastname = $User->get( 'lastname' ); | |
| $firstname_and_login = empty( $firstname ) ? $user_login : $firstname.' ('.$user_login.')'; | |
| $firstname_or_login = empty( $firstname ) ? $user_login : $firstname; | |
| $user_email = $User->email; | |
| $user_ID = $User->ID; | |
| $unsubscribe_key = '$secret_content_start$'.md5( $User->ID.$User->unsubscribe_key ).'$secret_content_end$'; | |
| if( isset( $params['template_mode'] ) && $params['template_mode'] == 'preview' ) | |
| { // Don't use real value of reminder key on preview email template, e.g. on review message of email campaign: | |
| $reminder_key = '$reminder_key$'; | |
| } | |
| else | |
| { // Use real value of reminder key: | |
| $reminder_key = $UserSettings->get( 'last_activation_reminder_key', $user_ID ); | |
| if( empty( $reminder_key ) && strpos( $text, '$reminder_key$' ) !== false ) | |
| { // If reminder key was not generated yet we need create it in order user can active account even if did request the activation email yet: | |
| $reminder_key = generate_random_key( 32 ); | |
| $UserSettings->set( 'last_activation_reminder_key', $reminder_key, $user_ID ); | |
| $UserSettings->dbupdate(); | |
| } | |
| } | |
| $newsletter_ID = isset( $params['enlt_ID'] ) ? $params['enlt_ID'] : ''; | |
| } | |
| else | |
| { // Get data of anonymous user: | |
| $username = $user_name; | |
| $user_login = $user_name; | |
| $firstname = $user_name; | |
| $lastname = $user_name; | |
| $firstname_and_login = $user_name; | |
| $firstname_or_login = $user_name; | |
| $user_email = $user_email; | |
| $user_ID = ''; | |
| $unsubscribe_key = ''; | |
| $reminder_key = ''; | |
| $newsletter_ID = ''; | |
| } | |
| $rpls_from = array( '$login$', '$username$', '$firstname$', '$lastname$', '$firstname_and_login$', '$firstname_or_login$', '$email$', '$user_ID$', '$unsubscribe_key$', '$reminder_key$', '$newsletter_ID$' ); | |
| $rpls_to = array( $user_login, $username, $firstname, $lastname, $firstname_and_login, $firstname_or_login, $user_email, $user_ID, $unsubscribe_key, $reminder_key, $newsletter_ID ); | |
| return str_replace( $rpls_from, $rpls_to, $text ); | |
| } | |
| /** | |
| * Get a mail message text by template name | |
| * | |
| * @param string Template name | |
| * @param string Email format ( auto | html | text ) | |
| * @param array Params | |
| * @param object User | |
| * @return string|array Mail message OR Array of the email contents when message is multipart content | |
| */ | |
| function mail_template( $template_name, $format = 'auto', $params = array(), $User = NULL ) | |
| { | |
| global $current_charset; | |
| global $track_email_image_load, $track_email_click_html, $track_email_click_plain_text; | |
| $params = array_merge( array( | |
| 'add_email_tracking' => true, | |
| 'template_parts' => array( | |
| 'header' => NULL, | |
| 'footer' => NULL | |
| ), | |
| 'default_template_tag' => NULL | |
| ), $params ); | |
| // Set this param to know current template name inside code of email templates like header and footer: | |
| $params['template_name'] = $template_name; | |
| if( !empty( $params['locale'] ) ) | |
| { // Switch to locale for current email template | |
| locale_temp_switch( $params['locale'] ); | |
| } | |
| // Set extension of template | |
| $template_exts = array(); | |
| switch( $format ) | |
| { | |
| case 'auto': | |
| // $template_exts['non-mime'] = '.txt.php'; // The area that is ignored by MIME-compliant clients | |
| $template_exts['text'] = '.txt.php'; | |
| $template_exts['html'] = '.html.php'; | |
| $boundary = $params['boundary']; | |
| $boundary_alt = 'b2evo-alt-'.md5( rand() ); | |
| $template_headers = array( | |
| 'text' => 'Content-Type: text/plain; charset='.$current_charset, | |
| 'html' => 'Content-Type: text/html; charset='.$current_charset, | |
| ); | |
| // Store all contents in this array for multipart message | |
| $template_contents = array( | |
| 'charset' => $current_charset, // Charset for email message | |
| 'full' => '', // Full content with html and plain | |
| 'html' => '', // HTML | |
| 'text' => '', // Plain text | |
| ); | |
| break; | |
| case 'html': | |
| $template_exts['html'] = '.html.php'; | |
| break; | |
| case 'text': | |
| $template_exts['text'] = '.txt.php'; | |
| break; | |
| } | |
| $template_message = ''; | |
| if( isset( $boundary, $boundary_alt ) ) | |
| { // Start new boundary content | |
| $template_message .= "\n".'--'.$boundary."\n"; | |
| $template_message .= 'Content-Type: multipart/alternative; boundary="'.$boundary_alt.'"'."\n\n"; | |
| } | |
| foreach( $template_exts as $format => $ext ) | |
| { | |
| $formated_message = ''; | |
| if( isset( $boundary, $boundary_alt ) && $format != 'non-mime' ) | |
| { // Start new boundary alt content | |
| $template_message .= "\n".'--'.$boundary_alt."\n"; | |
| } | |
| if( isset( $template_headers[ $format ] ) ) | |
| { // Header data for each content | |
| $template_message .= $template_headers[ $format ]."\n\n"; | |
| } | |
| // Get mail template | |
| ob_start(); | |
| emailskin_include( $template_name.$ext, $params ); | |
| $formated_message .= ob_get_clean(); | |
| if( ! empty( $User ) ) | |
| { // Replace $login$ with gender colored link + icon in HTML format, and with simple login text in PLAIN TEXT format | |
| $formated_message = mail_autoinsert_user_data( $formated_message, $User, $format, NULL, NULL, $params ); | |
| } | |
| elseif( ! empty( $params['anonymous_recipient_name'] ) ) | |
| { // Replace anonymous name: | |
| $formated_message = str_replace( '$name$', $params['anonymous_recipient_name'], $formated_message ); | |
| if( ! empty( $params['anonymous_unsubscribe_key'] ) ) | |
| { // Replace anonymous unsubscribe key: | |
| $formated_message = str_replace( '$unsubscribe_key$', $params['anonymous_unsubscribe_key'], $formated_message ); | |
| } | |
| } | |
| if( $params['add_email_tracking'] ) | |
| { | |
| $tracking_params = array( | |
| 'content_type' => $format, | |
| 'image_load' => isset( $track_email_image_load ) ? $track_email_image_load : true, | |
| 'link_click_html' => isset( $track_email_click_html ) ? $track_email_click_html : true , | |
| 'link_click_text' => isset( $track_email_click_plain_text ) ? $track_email_click_plain_text : true, | |
| 'template_parts' => $params['template_parts'], | |
| 'default_template_tag' => $params['default_template_tag'] | |
| ); | |
| $formated_message = add_email_tracking( $formated_message, '$mail_log_ID$', '$email_key$', $tracking_params ); | |
| } | |
| // Remove template parts markers | |
| $template_part_markers = array(); | |
| foreach( $params['template_parts'] as $part => $row ) | |
| { | |
| $template_part_markers[] = '$template-content-'.$part.'-start$'; | |
| $template_part_markers[] = '$template-content-'.$part.'-end$'; | |
| } | |
| $formated_message = str_replace( $template_part_markers, '', $formated_message ); | |
| $template_message .= $formated_message; | |
| if( isset( $template_contents ) ) | |
| { // Multipart content | |
| $template_contents[ $format ] = $formated_message; | |
| } | |
| } | |
| if( isset( $boundary, $boundary_alt ) ) | |
| { // End all boundary contents | |
| $template_message .= "\n".'--'.$boundary_alt.'--'."\n"; | |
| $template_message .= "\n".'--'.$boundary.'--'."\n"; | |
| } | |
| if( !empty( $params['locale'] ) ) | |
| { // Restore previous locale | |
| locale_restore_previous(); | |
| } | |
| if( isset( $template_contents ) ) | |
| { // Return array for multipart content | |
| $template_contents['full'] = $template_message; | |
| return $template_contents; | |
| } | |
| else | |
| { // Return string if email message contains one content (html or text) | |
| return $template_message; | |
| } | |
| } | |
| /** | |
| * Include email template from folder /skins_email/custom/ or /skins_email/ | |
| * | |
| * @param string Template name | |
| * @param array Params | |
| */ | |
| function emailskin_include( $template_name, $params = array(), $template_part = NULL ) | |
| { | |
| global $emailskins_path, $rsc_url; | |
| /** | |
| * @var Log | |
| */ | |
| global $Debuglog; | |
| global $Timer; | |
| $timer_name = 'emailskin_include('.$template_name.')'; | |
| $Timer->resume( $timer_name ); | |
| $is_customized = false; | |
| // Try to include custom template firstly | |
| $template_path = $emailskins_path.'custom/'.$template_name; | |
| if( file_exists( $template_path ) ) | |
| { // Include custom template file if it exists | |
| $Debuglog->add( 'emailskin_include: '.rel_path_to_base( $template_path ), 'skins' ); | |
| if( ! empty( $template_part ) ) | |
| { | |
| echo '$template-content-'.$template_part.'-start$'; | |
| } | |
| require $template_path; | |
| if( ! empty( $template_part ) ) | |
| { | |
| echo '$template-content-'.$template_part.'-end$'; | |
| } | |
| // This template is customized, Don't include standard template | |
| $is_customized = true; | |
| } | |
| if( !$is_customized ) | |
| { // Try to include standard template only if custom template doesn't exist | |
| $template_path = $emailskins_path.$template_name; | |
| if( file_exists( $template_path ) ) | |
| { // Include standard template file if it exists | |
| $Debuglog->add( 'emailskin_include: '.rel_path_to_base( $template_path ), 'skins' ); | |
| if( ! empty( $template_part ) ) | |
| { | |
| echo '$template-content-'.$template_part.'-start$'; | |
| } | |
| require $template_path; | |
| if( ! empty( $template_part ) ) | |
| { | |
| echo '$template-content-'.$template_part.'-end$'; | |
| } | |
| } | |
| } | |
| $Timer->pause( $timer_name ); | |
| } | |
| /** | |
| * Get attribute "style" by class name for element in email templates | |
| * | |
| * @param string Class name | |
| * @param boolean TRUE to return string as ' style="css_properties"' otherwise only 'css_properties' | |
| * @return string | |
| */ | |
| function emailskin_style( $class, $set_attr_name = true ) | |
| { | |
| global $emailskins_styles; | |
| if( ! is_array( $emailskins_styles ) ) | |
| { // Load email styles only first time | |
| global $emailskins_path; | |
| require_once $emailskins_path.'_email_style.php'; | |
| foreach( $emailskins_styles as $classes => $styles ) | |
| { | |
| if( strpos( $classes, ',' ) !== false ) | |
| { // This style is used for several classes | |
| unset( $emailskins_styles[ $classes ] ); | |
| $classes = explode( ',', $classes ); | |
| foreach( $classes as $class_name ) | |
| { | |
| $class_name = trim( $class_name ); | |
| if( isset( $emailskins_styles[ $class_name ] ) ) | |
| { | |
| $emailskins_styles[ $class_name ] .= $styles; | |
| } | |
| else | |
| { | |
| $emailskins_styles[ $class_name ] = $styles; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if( strpos( $class, '+' ) !== false ) | |
| { // Several classes should be applied this | |
| $classes = explode( '+', $class ); | |
| $style = ''; | |
| foreach( $classes as $c => $class ) | |
| { | |
| $style .= emailskin_style( $class, false ); | |
| } | |
| return empty( $style ) ? '' : ( $set_attr_name ? ' style="'.$style.'"' : $style ); | |
| } | |
| elseif( isset( $emailskins_styles[ $class ] ) ) | |
| { // One class | |
| $style = trim( str_replace( array( "\r", "\n", "\t" ), '', $emailskins_styles[ $class ] ) ); | |
| $style = str_replace( ': ', ':', $style ); | |
| return $set_attr_name ? ' style="'.$style.'"' : $style; | |
| } | |
| return ''; | |
| } | |
| /** | |
| * If first parameter evaluates to true printf() gets called using the first parameter | |
| * as args and the second parameter as print-pattern | |
| * | |
| * @param mixed variable to test and output if it's true or $disp_none is given | |
| * @param string printf-pattern to use (%s gets replaced by $var) | |
| * @param string printf-pattern to use, if $var is numeric and > 1 (%s gets replaced by $var) | |
| * @param string printf-pattern to use if $var evaluates to false (%s gets replaced by $var) | |
| */ | |
| function disp_cond( $var, $disp_one, $disp_more = NULL, $disp_none = NULL ) | |
| { | |
| if( is_numeric($var) && $var > 1 ) | |
| { | |
| printf( ( $disp_more === NULL ? $disp_one : $disp_more ), $var ); | |
| return true; | |
| } | |
| elseif( $var ) | |
| { | |
| printf( $disp_one, $var ); | |
| return true; | |
| } | |
| else | |
| { | |
| if( $disp_none !== NULL ) | |
| { | |
| printf( $disp_none, $var ); | |
| return false; | |
| } | |
| } | |
| } | |
| /** | |
| * Create IMG tag for an action icon. | |
| * | |
| * @param string TITLE text (IMG and A link) | |
| * @param string icon code for {@link get_icon()} | |
| * @param string URL where the icon gets linked to (empty to not wrap the icon in a link) | |
| * @param string word to be displayed after icon (if no icon gets displayed, $title will be used instead!) | |
| * @param integer 1-5: weight of the icon. The icon will be displayed only if its weight is >= than the user setting threshold. | |
| * Use 5, if it's a required icon - all others could get disabled by the user. (Default: 4) | |
| * @param integer 1-5: weight of the word. The word will be displayed only if its weight is >= than the user setting threshold. | |
| * (Default: 1) | |
| * @param array Additional attributes to the A tag. The values must be properly encoded for html output (e.g. quotes). | |
| * It may also contain these params: | |
| * - 'use_js_popup': if true, the link gets opened as JS popup. You must also pass an "id" attribute for this! | |
| * - 'use_js_size': use this to override the default popup size ("500, 400") | |
| * - 'class': defaults to 'action_icon', if not set; use "" to not use it | |
| * @param array Attributes for the icon | |
| * @return string The generated action icon link. | |
| */ | |
| function action_icon( $title, $icon, $url, $word = NULL, $icon_weight = NULL, $word_weight = NULL, $link_attribs = array(), $icon_attribs = array() ) | |
| { | |
| global $UserSettings; | |
| $link_attribs['href'] = $url; | |
| $link_attribs['title'] = $title; | |
| if( is_null($icon_weight) ) | |
| { | |
| $icon_weight = 4; | |
| } | |
| if( is_null($word_weight) ) | |
| { | |
| $word_weight = 1; | |
| } | |
| if( ! isset($link_attribs['class']) ) | |
| { | |
| $link_attribs['class'] = 'action_icon'; | |
| } | |
| if( get_icon( $icon, 'rollover' ) ) | |
| { | |
| if( empty($link_attribs['class']) ) | |
| { | |
| $link_attribs['class'] = 'rollover'; | |
| } | |
| else | |
| { | |
| $link_attribs['class'] .= ' rollover'; | |
| } | |
| if( get_icon( $icon, 'sprite' ) ) | |
| { // Set class "rollover_sprite" If image uses sprite | |
| $link_attribs['class'] .= '_sprite'; | |
| } | |
| } | |
| //$link_attribs['class'] .= $icon != '' ? ' '.$icon : ' noicon'; | |
| // "use_js_popup": open link in a JS popup | |
| // TODO: this needs to be rewritten with jQuery instead | |
| if( false && ! empty($link_attribs['use_js_popup']) ) | |
| { | |
| $popup_js = 'var win = new PopupWindow(); win.setUrl( \''.$link_attribs['href'].'\' ); win.setSize( ); '; | |
| if( isset($link_attribs['use_js_size']) ) | |
| { | |
| if( ! empty($link_attribs['use_js_size']) ) | |
| { | |
| $popup_size = $link_attribs['use_js_size']; | |
| } | |
| } | |
| else | |
| { | |
| $popup_size = '500, 400'; | |
| } | |
| if( isset($popup_size) ) | |
| { | |
| $popup_js .= 'win.setSize( '.$popup_size.' ); '; | |
| } | |
| $popup_js .= 'win.showPopup(\''.$link_attribs['id'].'\'); return false;'; | |
| if( empty( $link_attribs['onclick'] ) ) | |
| { | |
| $link_attribs['onclick'] = $popup_js; | |
| } | |
| else | |
| { | |
| $link_attribs['onclick'] .= $popup_js; | |
| } | |
| unset($link_attribs['use_js_popup']); | |
| unset($link_attribs['use_js_size']); | |
| } | |
| $display_icon = empty( $UserSettings ) ? false : ($icon_weight >= $UserSettings->get('action_icon_threshold')); | |
| $display_word = empty( $UserSettings ) ? false : ($word_weight >= $UserSettings->get('action_word_threshold')); | |
| $a_body = ''; | |
| if( $display_icon || ! $display_word ) | |
| { // We MUST display an action icon in order to make the user happy: | |
| // OR we default to icon because the user doesn't want the word either!! | |
| $icon_attribs = array_merge( array( | |
| 'title' => false, // No need to set attribute "ttile" for icon because parent <a> already has it | |
| ), $icon_attribs ); | |
| if( $icon_s = get_icon( $icon, 'imgtag', $icon_attribs, true ) ) | |
| { | |
| $a_body .= $icon_s; | |
| } | |
| else | |
| { // fallback to word | |
| $display_word = true; | |
| } | |
| } | |
| if( $display_word ) | |
| { // We MUST display an action word in order to make the user happy: | |
| if( $display_icon ) | |
| { // We already have an icon, display a SHORT word: | |
| if( !empty($word) ) | |
| { // We have provided a short word: | |
| $a_body .= $word; | |
| } | |
| else | |
| { // We fall back to alt: | |
| $a_body .= get_icon( $icon, 'legend' ); | |
| } | |
| } | |
| else | |
| { // No icon display, let's display a LONG word/text: | |
| $a_body .= trim( $title, ' .!' ); | |
| } | |
| // Add class "hoverlink" for icon with text | |
| $link_attribs['class'] .= ' hoverlink'; | |
| } | |
| // Format title attribute because it may contains the unexpected chars from translatable strings: | |
| if( isset( $link_attribs['title'] ) ) | |
| { | |
| $link_attribs['title'] = format_to_output( $link_attribs['title'], 'htmlattr' ); | |
| } | |
| // NOTE: We do not use format_to_output with get_field_attribs_as_string() here, because it interferes with the Results class (eval() fails on entitied quotes..) (blueyed) | |
| return '<a'.get_field_attribs_as_string( $link_attribs, false ).'>'.$a_body.'</a>'; | |
| } | |
| /** | |
| * Get properties of an icon. | |
| * | |
| * Note: to get a file type icon, use {@link File::get_icon()} instead. | |
| * | |
| * @uses get_icon_info() | |
| * @param string icon for what? (key) | |
| * @param string what to return for that icon ('imgtag', 'alt', 'legend', 'file', 'url', 'size' {@link imgsize()}) | |
| * @param array additional params | |
| * - 'class' => class name when getting 'imgtag', | |
| * - 'size' => param for 'size', | |
| * - 'title' => title attribute for 'imgtag' | |
| * @param boolean true to include this icon into the legend at the bottom of the page (works for 'imgtag' only) | |
| * @return mixed False on failure, string on success. | |
| */ | |
| function get_icon( $iconKey, $what = 'imgtag', $params = NULL, $include_in_legend = false ) | |
| { | |
| global $admin_subdir, $Debuglog, $use_strict; | |
| global $conf_path; | |
| global $rsc_path, $rsc_uri; | |
| if( ! function_exists('get_icon_info') ) | |
| { | |
| require_once $conf_path.'_icons.php'; | |
| } | |
| $icon = get_icon_info($iconKey); | |
| if( ! $icon ) | |
| { | |
| $Debuglog->add('No image defined for '.var_export( $iconKey, true ).'!', 'icons'); | |
| return false; | |
| } | |
| if( !isset( $icon['file'] ) && $what != 'imgtag' ) | |
| { | |
| $icon['file'] = 'icons/icons_sprite.png'; | |
| } | |
| switch( $what ) | |
| { | |
| case 'rollover': | |
| if( isset( $icon['rollover'] ) ) | |
| { // Image has rollover available | |
| global $b2evo_icons_type; | |
| if( isset( $b2evo_icons_type ) && ( ! empty( $icon['glyph'] ) || ! empty( $icon['fa'] ) ) ) | |
| { // Glyph and font-awesome icons don't have rollover effect | |
| return false; | |
| } | |
| return $icon['rollover']; | |
| } | |
| return false; | |
| /* BREAK */ | |
| case 'file': | |
| return $rsc_path.$icon['file']; | |
| /* BREAK */ | |
| case 'alt': | |
| if( isset( $icon['alt'] ) ) | |
| { // alt tag from $map_iconfiles | |
| return $icon['alt']; | |
| } | |
| else | |
| { // fallback to $iconKey as alt-tag | |
| return $iconKey; | |
| } | |
| /* BREAK */ | |
| case 'legend': | |
| if( isset( $icon['legend'] ) ) | |
| { // legend tag from $map_iconfiles | |
| return $icon['legend']; | |
| } | |
| else | |
| if( isset( $icon['alt'] ) ) | |
| { // alt tag from $map_iconfiles | |
| return $icon['alt']; | |
| } | |
| else | |
| { // fallback to $iconKey as alt-tag | |
| return $iconKey; | |
| } | |
| /* BREAK */ | |
| case 'class': | |
| if( isset($icon['class']) ) | |
| { | |
| return $icon['class']; | |
| } | |
| else | |
| { | |
| return ''; | |
| } | |
| /* BREAK */ | |
| case 'url': | |
| return $rsc_uri.$icon['file']; | |
| /* BREAK */ | |
| case 'size': | |
| if( !isset( $icon['size'] ) ) | |
| { | |
| $Debuglog->add( 'No iconsize for ['.$iconKey.']', 'icons' ); | |
| $icon['size'] = imgsize( $rsc_path.$icon['file'] ); | |
| } | |
| switch( $params['size'] ) | |
| { | |
| case 'width': | |
| return $icon['size'][0]; | |
| case 'height': | |
| return $icon['size'][1]; | |
| case 'widthxheight': | |
| return $icon['size'][0].'x'.$icon['size'][1]; | |
| case 'width': | |
| return $icon['size'][0]; | |
| case 'string': | |
| return 'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'"'; | |
| default: | |
| return $icon['size']; | |
| } | |
| /* BREAK */ | |
| case 'xy': | |
| if( isset( $icon['xy'] ) ) | |
| { // Return data for style property "background-position" | |
| return "-".$icon['xy'][0]."px -".$icon['xy'][1]."px"; | |
| } | |
| return false; | |
| case 'sprite': | |
| if( isset( $icon['xy'] ) ) | |
| { // Image uses spite file | |
| return true; | |
| } | |
| return false; | |
| /* BREAK */ | |
| case 'imgtag': | |
| global $b2evo_icons_type; | |
| if( isset( $b2evo_icons_type ) ) | |
| { // Specific icons type is defined | |
| $current_icons_type = $b2evo_icons_type; | |
| if( $current_icons_type == 'fontawesome-glyphicons' ) | |
| { // Use fontawesome icons as a priority over the glyphicons | |
| $current_icons_type = isset( $icon['fa'] ) ? 'fontawesome' : 'glyphicons'; | |
| } | |
| switch( $current_icons_type ) | |
| { | |
| case 'glyphicons': | |
| // Use glyph icons of bootstrap | |
| $icon_class_prefix = 'glyphicon glyphicon-'; | |
| $icon_param_name = 'glyph'; | |
| $icon_content = ' '; | |
| break; | |
| case 'fontawesome': | |
| // Use the icons from http://fortawesome.github.io/Font-Awesome/icons/ | |
| $icon_class_prefix = 'fa fa-'; | |
| $icon_param_name = 'fa'; | |
| $icon_content = ''; | |
| break; | |
| } | |
| } | |
| if( isset( $icon_class_prefix ) && ! empty( $icon[ $icon_param_name ] ) ) | |
| { // Use glyph or fa icon if it is defined in icons config | |
| if( isset( $params['class'] ) ) | |
| { // Get class from params | |
| $params['class'] = $icon_class_prefix.$icon[ $icon_param_name ].' '.$params['class']; | |
| } | |
| else | |
| { // Set default class | |
| $params['class'] = $icon_class_prefix.$icon[ $icon_param_name ]; | |
| } | |
| $styles = array(); | |
| if( isset( $params['color'] ) ) | |
| { // Set color from params: | |
| $styles[] = 'color:'.$params['color']; | |
| unset( $params['color'] ); | |
| } | |
| elseif( isset( $icon['color-'.$icon_param_name] ) ) | |
| { // Set a color for icon only for current type | |
| if( $icon['color-'.$icon_param_name] != 'default' ) | |
| { | |
| $styles[] = 'color:'.$icon['color-'.$icon_param_name]; | |
| } | |
| } | |
| elseif( isset( $icon['color'] ) ) | |
| { // Set a color for icon for all types | |
| if( $icon['color'] != 'default' ) | |
| { | |
| $styles[] = 'color:'.$icon['color']; | |
| } | |
| } | |
| if( isset( $icon['color-over'] ) ) | |
| { // Set a color for mouse over event | |
| $params['data-color'] = $icon['color-over']; | |
| } | |
| if( isset( $icon['toggle-'.$icon_param_name] ) ) | |
| { // Set a color for mouse over event | |
| $params['data-toggle'] = $icon['toggle-'.$icon_param_name]; | |
| } | |
| if( ! isset( $params['title'] ) ) | |
| { // Use 'alt' for 'title' | |
| if( isset( $params['alt'] ) ) | |
| { | |
| $params['title'] = $params['alt']; | |
| unset( $params['alt'] ); | |
| } | |
| else if( ! isset( $params['alt'] ) && isset( $icon['alt'] ) ) | |
| { | |
| $params['title'] = $icon['alt']; | |
| } | |
| } | |
| if( isset( $params['title'] ) && $params['title'] === false ) | |
| { // Disable title: | |
| unset( $params['title'] ); | |
| } | |
| // Format title and alt attributes because they may contain the unexpected chars from translatable strings: | |
| if( isset( $params['title'] ) ) | |
| { | |
| // Use 'htmlspecialchars' format instead of 'htmlattr' because html tags should not be stripped e.g. in bootsrap tooltips: | |
| $params['title'] = format_to_output( $params['title'], 'htmlspecialchars' ); | |
| } | |
| if( isset( $params['alt'] ) ) | |
| { | |
| $params['alt'] = format_to_output( $params['alt'], 'htmlattr' ); | |
| } | |
| if( isset( $icon['size-'.$icon_param_name] ) ) | |
| { // Set a size for icon only for current type | |
| if( isset( $icon['size-'.$icon_param_name][0] ) ) | |
| { // Width | |
| $styles['width'] = 'width:'.$icon['size-'.$icon_param_name][0].'px'; | |
| } | |
| if( isset( $icon['size-'.$icon_param_name][1] ) ) | |
| { // Height | |
| $styles['height'] = 'height:'.$icon['size-'.$icon_param_name][1].'px'; | |
| } | |
| } | |
| if( isset( $params['style'] ) ) | |
| { // Keep styles from params | |
| $styles[] = $params['style']; | |
| } | |
| if( ! empty( $styles ) ) | |
| { // Init attribute 'style' | |
| $params['style'] = implode( ';', $styles ); | |
| } | |
| // Add all the attributes: | |
| $params = get_field_attribs_as_string( $params, false ); | |
| $r = '<span'.$params.'>'.$icon_content.'</span>'; | |
| } | |
| elseif( ! isset( $icon['file'] ) ) | |
| { // Use span tag with sprite instead of img | |
| $styles = array(); | |
| if( isset( $params['xy'] ) ) | |
| { // Get background position from params | |
| $styles[] = "background-position: ".$params['xy'][0]."px ".$params['xy'][1]."px"; | |
| unset( $params['xy'] ); | |
| } | |
| else if( isset( $icon['xy'] ) ) | |
| { // Set background position in the icons_sprite.png | |
| $styles[] = "background-position: -".$icon['xy'][0]."px -".$icon['xy'][1]."px"; | |
| } | |
| if( isset( $params['size'] ) ) | |
| { // Get sizes from params | |
| $icon['size'] = $params['size']; | |
| unset( $params['size'] ); | |
| } | |
| if( isset( $icon['size'] ) ) | |
| { // Set width & height | |
| if( $icon['size'][0] != 16 ) | |
| { | |
| $styles[] = "width: ".$icon['size'][0]."px"; | |
| } | |
| if( $icon['size'][1] != 16 ) | |
| { | |
| $styles[] = "height: ".$icon['size'][1]."px; line-height: ".$icon['size'][1]."px"; | |
| } | |
| } | |
| if( isset( $params['style'] ) ) | |
| { // Get styles from params | |
| $styles[] = $params['style']; | |
| } | |
| if( count( $styles ) > 0 ) | |
| { | |
| $params['style'] = implode( '; ', $styles); | |
| } | |
| if( ! isset( $params['title'] ) ) | |
| { // Use 'alt' for 'title' | |
| if( isset( $params['alt'] ) ) | |
| { | |
| $params['title'] = $params['alt']; | |
| unset( $params['alt'] ); | |
| } | |
| else if( ! isset( $params['alt'] ) && isset( $icon['alt'] ) ) | |
| { | |
| $params['title'] = $icon['alt']; | |
| } | |
| } | |
| if( isset( $params['title'] ) && $params['title'] === false ) | |
| { // Disable title: | |
| unset( $params['title'] ); | |
| } | |
| // Format title and alt attributes because they may contain the unexpected chars from translatable strings: | |
| if( isset( $params['title'] ) ) | |
| { | |
| $params['title'] = format_to_output( $params['title'], 'htmlattr' ); | |
| } | |
| if( isset( $params['alt'] ) ) | |
| { | |
| $params['alt'] = format_to_output( $params['alt'], 'htmlattr' ); | |
| } | |
| if( isset( $params['class'] ) ) | |
| { // Get class from params | |
| $params['class'] = 'icon '.$params['class']; | |
| } | |
| else | |
| { // Set default class | |
| $params['class'] = 'icon'; | |
| } | |
| // Add all the attributes: | |
| $params = get_field_attribs_as_string( $params, false ); | |
| $r = '<span'.$params.'> </span>'; | |
| } | |
| else | |
| { // Use img tag | |
| $r = '<img src="'.$rsc_uri.$icon['file'].'" '; | |
| if( !$use_strict ) | |
| { // Include non CSS fallbacks - transitional only: | |
| $r .= 'border="0" align="top" '; | |
| } | |
| // Include class (will default to "icon"): | |
| if( ! isset( $params['class'] ) ) | |
| { | |
| if( isset($icon['class']) ) | |
| { // This icon has a class | |
| $params['class'] = $icon['class']; | |
| } | |
| else | |
| { | |
| $params['class'] = ''; | |
| } | |
| } | |
| // Include size (optional): | |
| if( isset( $icon['size'] ) ) | |
| { | |
| $r .= 'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'" '; | |
| } | |
| // Include alt (XHTML mandatory): | |
| if( ! isset( $params['alt'] ) ) | |
| { | |
| if( isset( $icon['alt'] ) ) | |
| { // alt-tag from $map_iconfiles | |
| $params['alt'] = $icon['alt']; | |
| } | |
| else | |
| { // $iconKey as alt-tag | |
| $params['alt'] = $iconKey; | |
| } | |
| } | |
| // Format alt attribute because it may contains the unexpected chars from translatable strings: | |
| if( isset( $params['alt'] ) ) | |
| { | |
| $params['alt'] = format_to_output( $params['alt'], 'htmlattr' ); | |
| } | |
| // Add all the attributes: | |
| $r .= get_field_attribs_as_string( $params, false ); | |
| // Close tag: | |
| $r .= '/>'; | |
| if( $include_in_legend && ( $IconLegend = & get_IconLegend() ) ) | |
| { // This icon should be included into the legend: | |
| $IconLegend->add_icon( $iconKey ); | |
| } | |
| } | |
| return $r; | |
| /* BREAK */ | |
| case 'noimg': | |
| global $b2evo_icons_type; | |
| if( isset( $b2evo_icons_type ) ) | |
| { // Specific icons type is defined | |
| $current_icons_type = $b2evo_icons_type; | |
| if( $current_icons_type == 'fontawesome-glyphicons' ) | |
| { // Use fontawesome icons as a priority over the glyphicons | |
| $current_icons_type = isset( $icon['fa'] ) ? 'fontawesome' : 'glyphicons'; | |
| } | |
| switch( $current_icons_type ) | |
| { | |
| case 'glyphicons': | |
| // Use glyph icons of bootstrap | |
| $icon_param_name = 'glyph'; | |
| break; | |
| case 'fontawesome': | |
| // Use the icons from http://fortawesome.github.io/Font-Awesome/icons/ | |
| $icon_param_name = 'fa'; | |
| break; | |
| } | |
| } | |
| $styles = array(); | |
| if( isset( $icon_param_name ) && ! empty( $icon[ $icon_param_name ] ) ) | |
| { // Use glyph or fa icon if it is defined in icons config | |
| if( isset( $icon['size-'.$icon_param_name] ) ) | |
| { // Set a size for icon only for current type | |
| if( isset( $icon['size-'.$icon_param_name][0] ) ) | |
| { // Width | |
| $styles['width'] = 'width:'.$icon['size-'.$icon_param_name][0].'px'; | |
| } | |
| if( isset( $icon['size-'.$icon_param_name][1] ) ) | |
| { // Height | |
| $styles['width'] = 'height:'.$icon['size-'.$icon_param_name][1].'px'; | |
| } | |
| if( isset( $icon['size'] ) ) | |
| { // Unset size for sprite icon | |
| unset( $icon['size'] ); | |
| } | |
| } | |
| } | |
| // Include size (optional): | |
| if( isset( $icon['size'] ) ) | |
| { | |
| $params['size'] = $icon['size']; | |
| } | |
| $styles[] = 'margin:0 2px'; | |
| if( isset( $params['style'] ) ) | |
| { // Keep styles from params | |
| $styles[] = $params['style']; | |
| } | |
| if( ! empty( $styles ) ) | |
| { // Init attribute 'style' | |
| $params['style'] = implode( ';', $styles ); | |
| } | |
| return get_icon( 'pixel', 'imgtag', $params ); | |
| /* BREAK */ | |
| /* | |
| $blank_icon = get_icon_info('pixel'); | |
| $r = '<img src="'.$rsc_uri.$blank_icon['file'].'" '; | |
| // TODO: dh> add this only for !$use_strict, like above? | |
| // Include non CSS fallbacks (needed by bozos... and basic skin): | |
| $r .= 'border="0" align="top" '; | |
| // Include class (will default to "noicon"): | |
| if( ! isset( $params['class'] ) ) | |
| { | |
| if( isset($icon['class']) ) | |
| { // This icon has a class | |
| $params['class'] = $icon['class']; | |
| } | |
| else | |
| { | |
| $params['class'] = 'no_icon'; | |
| } | |
| } | |
| // Include size (optional): | |
| if( isset( $icon['size'] ) ) | |
| { | |
| $r .= 'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'" '; | |
| } | |
| // Include alt (XHTML mandatory): | |
| if( ! isset( $params['alt'] ) ) | |
| { | |
| $params['alt'] = ''; | |
| } | |
| // Add all the attributes: | |
| $r .= get_field_attribs_as_string( $params, false ); | |
| // Close tag: | |
| $r .= '/>'; | |
| return $r;*/ | |
| /* BREAK */ | |
| } | |
| } | |
| /** | |
| * @param string date (YYYY-MM-DD) | |
| * @param string time | |
| */ | |
| function form_date( $date, $time = '' ) | |
| { | |
| return substr( $date.' ', 0, 10 ).' '.$time; | |
| } | |
| /** | |
| * Get list of client IP addresses from REMOTE_ADDR and HTTP_X_FORWARDED_FOR, | |
| * in this order. '' is used when no IP could be found. | |
| * | |
| * @param boolean True, to get only the first IP (probably REMOTE_ADDR) | |
| * @param boolean True, to convert IPv6 to IPv4 format | |
| * @return array|string Depends on first param. | |
| */ | |
| function get_ip_list( $firstOnly = false, $convert_to_ipv4 = false ) | |
| { | |
| $r = array(); | |
| if( ! empty( $_SERVER['REMOTE_ADDR'] ) ) | |
| { | |
| foreach( explode( ',', $_SERVER['REMOTE_ADDR'] ) as $l_ip ) | |
| { | |
| $l_ip = trim( $l_ip ); | |
| if( ! empty( $l_ip ) ) | |
| { | |
| if( $convert_to_ipv4 ) | |
| { // Convert IP address to IPv4 format(if it is in IPv6 format) | |
| $l_ip = int2ip( ip2int( $l_ip ) ); | |
| } | |
| $r[] = $l_ip; | |
| } | |
| } | |
| } | |
| if( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) | |
| { // IP(s) behind Proxy - this can be easily forged! | |
| foreach( explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ) as $l_ip ) | |
| { | |
| $l_ip = trim( $l_ip ); | |
| if( ! empty( $l_ip ) && $l_ip != 'unknown' ) | |
| { | |
| if( $convert_to_ipv4 ) | |
| { // Convert IP address to IPv4 format(if it is in IPv6 format) | |
| $l_ip = int2ip( ip2int( $l_ip ) ); | |
| } | |
| $r[] = $l_ip; | |
| } | |
| } | |
| } | |
| if( ! isset( $r[0] ) ) | |
| { // No IP found. | |
| $r[] = ''; | |
| } | |
| // Remove the duplicates | |
| $r = array_unique( $r ); | |
| return $firstOnly ? $r[0] : $r; | |
| } | |
| /** | |
| * Get list of IP addresses with link to back-office page if User has an access | |
| * | |
| * @param object|NULL User | |
| * @param array|NULL List of IP addresses | |
| * @param string Text of link, Use '#' to display IP address | |
| * @return array List of IP addresses | |
| */ | |
| function get_linked_ip_list( $ip_list = NULL, $User = NULL, $link_text = '#' ) | |
| { | |
| if( $User === NULL ) | |
| { // Get current User by default | |
| global $current_User; | |
| $User = & $current_User; | |
| } | |
| if( $ip_list === NULL ) | |
| { // Get IP addresses by function get_ip_list() | |
| $ip_list = get_ip_list( false, true ); | |
| } | |
| if( ! empty( $User ) && | |
| $User->check_perm( 'admin', 'restricted' ) && | |
| $User->check_perm( 'spamblacklist', 'view' ) ) | |
| { // User has an access to backoffice, Display a link for each IP address | |
| global $admin_url; | |
| foreach( $ip_list as $i => $ip_address ) | |
| { | |
| if( $link_text == '#' ) | |
| { // Use IP address aslink text | |
| $link_text = $ip_address; | |
| } | |
| $ip_list[ $i ] = '<a href="'.$admin_url.'?ctrl=antispam&tab3=ipranges&ip_address='.$ip_address.'">'.$link_text.'</a>'; | |
| } | |
| } | |
| return $ip_list; | |
| } | |
| /** | |
| * Get the base domain (without protocol and any subdomain) of an URL. | |
| * | |
| * Gets a max of 3 domain parts (x.y.tld) | |
| * | |
| * @param string URL | |
| * @return string the base domain (may become empty, if found invalid) | |
| */ | |
| function get_base_domain( $url ) | |
| { | |
| global $evo_charset; | |
| // Chop away the protocol part(http,htpps,ftp) and the path: | |
| $domain = preg_replace( '~^([a-z]+://)?([^:/#]+)(.*)$~i', '\\2', $url ); | |
| if( empty( $domain ) || preg_match( '~^(\d+\.)+\d+$~', $domain ) ) | |
| { // Empty or All numeric = IP address, don't try to cut it any further: | |
| return $domain; | |
| } | |
| // Get the base domain up to 2 or 3 levels (x.y.tld): | |
| // NOTE: "_" is not really valid, but for Windows it is.. | |
| // NOTE: \w includes "_" | |
| // Convert URL to IDN: | |
| $domain = idna_encode( $domain ); | |
| if( preg_match( '~\.(com|net|org|int|edu|gov|mil)$~i', $domain ) ) | |
| { // Use max 2 level domain for very well known TLDs: | |
| // (for example: "sub3.sub2.sub1.domain.com" will be "domain.com") | |
| $max_level = 2; | |
| } | |
| else | |
| { // Use max 3 level domain for all others: | |
| // (for example: "sub3.sub2.sub1.domain.fr" will be "sub1.domain.fr") | |
| $max_level = 3; | |
| } | |
| // Limit domain by 2 or 3 level depending on TLD: | |
| if( ! preg_match( '~ ( \w (\w|-|_)* \. ){0,'.( $max_level - 1 ).'} \w (\w|-|_)* $~ix', $domain, $match ) ) | |
| { // Return an empty if domain doesn't match to proper format: | |
| return ''; | |
| } | |
| // Convert all symbols of domain name to UTF-8: | |
| $domain = convert_charset( idna_decode( $match[0] ), $evo_charset, 'UTF-8' ); | |
| // Remove any prefix like "www.", "www2.", "www9999." and etc.: | |
| $domain = preg_replace( '~^www[0-9]*\.~i', '', $domain ); | |
| return $domain; | |
| } | |
| /** | |
| * Generate login from registration information | |
| * | |
| * @param string Email address | |
| * @param string First name | |
| * @param string Last name | |
| * @param string Nickname | |
| * @param boolean Use random alphanumeric string as login | |
| * @return string Generated login | |
| */ | |
| function generate_login_from_register_info( $email = NULL, $firstname = NULL, $lastname = NULL, $nickname = NULL, $use_random = false ) | |
| { | |
| global $Settings; | |
| if( ! empty( $firstname ) || ( ! empty( $lastname ) && ( $Settings->get( 'registration_no_username') == 'firstname.lastname' ) ) ) | |
| { // Firstname or lastname given, let's use these: | |
| $login = array(); | |
| if( ! empty( $firstname ) ) | |
| { | |
| $login[] = trim( $firstname ); | |
| } | |
| if( $Settings->get( 'registration_no_username' ) == 'firstname.lastname' ) | |
| { // We can use lastname too: | |
| if( ! empty( $lastname ) ) | |
| { | |
| $login[] = trim( $lastname ); | |
| } | |
| } | |
| $login = preg_replace( '/[\s]+/', '_', utf8_strtolower( implode( '.', $login ) ) ); | |
| $login = generate_login_from_string( $login ); | |
| } | |
| elseif( ! empty( $email ) ) | |
| { // Get the login from email address: | |
| $login = preg_replace( '/^([^@]+)@(.+)$/', '$1', utf8_strtolower( $email ) ); | |
| $login = preg_replace( '/[\'"><@\s]/', '', $login ); | |
| if( strpos( $login, '.' ) && ( $Settings->get( 'registration_no_username' ) == 'firstname' ) ) | |
| { // Get only the part before the "." if it has one | |
| $temp_login = $login; | |
| $login = substr( $login, 0, strpos( $login, '.' ) ); | |
| $login = generate_login_from_string( $login ); | |
| if( empty( $login ) ) | |
| { // Resulting login empty, use full email address | |
| $login = generate_login_from_string( $temp_login ); | |
| } | |
| } | |
| else | |
| { | |
| $login = generate_login_from_string( $login ); | |
| } | |
| } | |
| elseif( ! empty( $nickname ) ) | |
| { | |
| $login = preg_replace( '/[\s]+/', '_', utf8_strtolower( trim( $nickname ) ) ); | |
| $login = generate_login_from_string( $login ); | |
| } | |
| elseif( $use_random ) | |
| { // Nothing else to use as login, use random numbers: | |
| $login = 'user_'.rand( 1, 999 ); | |
| $login = generate_login_from_string( $login ); | |
| } | |
| else | |
| { | |
| return ''; | |
| } | |
| return $login; | |
| } | |
| /** | |
| * Generate login from string | |
| * | |
| * @param string string to generate login from | |
| * @return string login | |
| */ | |
| function generate_login_from_string( $login ) | |
| { | |
| global $Settings; | |
| // Normalize login | |
| load_funcs('locales/_charset.funcs.php'); | |
| $login = replace_special_chars( $login, NULL, true ); | |
| if( $Settings->get( 'strict_logins' ) ) | |
| { // We allow only the plain ACSII characters, digits, the chars _ and . and - | |
| $login = preg_replace( '/[^A-Za-z0-9_.\-]/', '', $login ); | |
| } | |
| else | |
| { // We allow any character that is not explicitly forbidden in Step 1 | |
| // Enforce additional limitations | |
| $login = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '', $login ); // Kill octets | |
| $login = preg_replace( '/&.+?;/', '', $login ); // Kill entities | |
| } | |
| $login = preg_replace( '/^usr_/i', '', $login ); | |
| // Trim to allowed login length | |
| $max_login_length = 20; | |
| if( strlen( $login ) > $max_login_length ) | |
| { | |
| $login = substr( $login, 0, $max_login_length ); | |
| } | |
| if( ! empty( $login ) ) | |
| { | |
| // Check and search free login name if current is already in use | |
| $login_name = $login; | |
| $login_number = 1; | |
| $UserCache = & get_UserCache(); | |
| while( $UserCache->get_by_login( $login_name ) ) | |
| { | |
| $num_suffix_length = strlen( $login_number ); | |
| if( strlen( $login_name ) + $num_suffix_length > $max_login_length ) | |
| { | |
| $login_name = substr( $login, 0, $max_login_length - $num_suffix_length ).$login_number; | |
| } | |
| else | |
| { | |
| $login_name = $login.$login_number; | |
| } | |
| $login_number++; | |
| } | |
| $login = $login_name; | |
| } | |
| return $login; | |
| } | |
| /** | |
| * Generate a valid key of size $length. | |
| * | |
| * @param integer length of key | |
| * @param string chars to use in generated key | |
| * @return string key | |
| */ | |
| function generate_random_key( $length = 32, $keychars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ) | |
| { | |
| $key = ''; | |
| $rnd_max = strlen($keychars) - 1; | |
| for( $i = 0; $i < $length; $i++ ) | |
| { | |
| $key .= $keychars[random_int(0, $rnd_max)]; // get a random character out of $keychars | |
| } | |
| return $key; | |
| } | |
| /** | |
| * Generate a random password with no ambiguous chars | |
| * | |
| * @param integer length of password | |
| * @return string password | |
| */ | |
| function generate_random_passwd( $length = NULL ) | |
| { | |
| // fp> NOTE: do not include any characters that would make autogenerated passwords ambiguous | |
| // 1 (one) vs l (L) vs I (i) | |
| // O (letter) vs 0 (digit) | |
| if( empty($length) ) | |
| { | |
| $length = rand( 8, 14 ); | |
| } | |
| return generate_random_key( $length, 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789' ); | |
| } | |
| function is_create_action( $action ) | |
| { | |
| $action_parts = explode( '_', $action ); | |
| switch( $action_parts[0] ) | |
| { | |
| case 'new': | |
| case 'new_switchtab': | |
| case 'copy': | |
| case 'create': // we return in this state after a validation error | |
| case 'preview': | |
| return true; | |
| case 'edit': | |
| case 'edit_switchtab': | |
| case 'propose': | |
| case 'update': // we return in this state after a validation error | |
| case 'delete': | |
| // The following one's a bit far fetched, but can happen if we have no sheet display: | |
| case 'unlink': | |
| case 'view': | |
| case 'extract': | |
| return false; | |
| default: | |
| debug_die( 'Unhandled action in form: '.strip_tags($action_parts[0]) ); | |
| } | |
| } | |
| /** | |
| * Compact a date in a number keeping only integer value of the string | |
| * | |
| * @param string date | |
| */ | |
| function compact_date( $date ) | |
| { | |
| return preg_replace( '#[^0-9]#', '', $date ); | |
| } | |
| /** | |
| * Decompact a date in a date format ( Y-m-d h:m:s ) | |
| * | |
| * @param string date | |
| */ | |
| function decompact_date( $date ) | |
| { | |
| $date0 = $date; | |
| return substr($date0,0,4).'-'.substr($date0,4,2).'-'.substr($date0,6,2).' ' | |
| .substr($date0,8,2).':'.substr($date0,10,2).':'.substr($date0,12,2); | |
| } | |
| /** | |
| * Check the format of the phone number param and | |
| * format it in a french number if it is. | |
| * | |
| * @param string phone number | |
| */ | |
| function format_phone( $phone, $hide_country_dialing_code_if_same_as_locale = true ) | |
| { | |
| global $CountryCache; | |
| $dialing_code = NULL; | |
| if( substr( $phone, 0, 1 ) == '+' ) | |
| { // We have a dialing code in the phone, so we extract it: | |
| $dialing_code = $CountryCache->extract_country_dialing_code( substr( $phone, 1 ) ); | |
| } | |
| if( !is_null( $dialing_code ) && ( locale_dialing_code() == $dialing_code ) | |
| && $hide_country_dialing_code_if_same_as_locale ) | |
| { // The phone dialing code is same as locale and we want to hide it in this case | |
| if( ( strlen( $phone ) - strlen( $dialing_code ) ) == 10 ) | |
| { // We can format it like a french phone number ( 0x.xx.xx.xx.xx ) | |
| $phone_formated = format_french_phone( '0'.substr( $phone, strlen( $dialing_code )+1 ) ); | |
| } | |
| else | |
| { // ( 0xxxxxxxxxxxxxx ) | |
| $phone_formated = '0'.substr( $phone, strlen( $dialing_code )+1 ); | |
| } | |
| } | |
| elseif( !is_null( $dialing_code ) ) | |
| { // Phone has a dialing code | |
| if( ( strlen( $phone ) - strlen( $dialing_code ) ) == 10 ) | |
| { // We can format it like a french phone number with the dialing code ( +dialing x.xx.xx.xx.xx ) | |
| $phone_formated = '+'.$dialing_code.format_french_phone( ' '.substr( $phone, strlen( $dialing_code )+1 ) ); | |
| } | |
| else | |
| { // ( +dialing xxxxxxxxxxx ) | |
| $phone_formated = '+'.$dialing_code.' '.substr( $phone, strlen( $dialing_code )+1 ); | |
| } | |
| } | |
| else | |
| { | |
| if( strlen( $phone ) == 10 ) | |
| { // We can format it like a french phone number ( xx.xx.xx.xx.xx ) | |
| $phone_formated = format_french_phone( $phone ); | |
| } | |
| else | |
| { // We don't format phone: TODO generic format phone ( xxxxxxxxxxxxxxxx ) | |
| $phone_formated = $phone; | |
| } | |
| } | |
| return $phone_formated; | |
| } | |
| /** | |
| * Format a string in a french phone number | |
| * | |
| * @param string phone number | |
| */ | |
| function format_french_phone( $phone ) | |
| { | |
| return substr($phone, 0 , 2).'.'.substr($phone, 2, 2).'.'.substr($phone, 4, 2) | |
| .'.'.substr($phone, 6, 2).'.'.substr($phone, 8, 2); | |
| } | |
| /** | |
| * Get the manual url for the given topic | |
| * | |
| * @param string topic | |
| * @return string url to the manual | |
| */ | |
| function get_manual_url( $topic ) | |
| { | |
| // fp> TODO: this below is a temmporary hack while we work on the new manual: | |
| return 'http://b2evolution.net/man/'.str_replace( '_', '-', strtolower( rawurlencode( $topic ) ) ); | |
| } | |
| /** | |
| * Generate a link to a online help resource. | |
| * testing the concept of online help (aka webhelp). | |
| * this function should be relocated somewhere better if it is taken onboard by the project | |
| * | |
| * @todo replace [?] with icon, | |
| * @todo write url suffix dynamically based on topic and language | |
| * | |
| * QUESTION: launch new window with javascript maybe? | |
| * @param string Topic | |
| * The topic should be in a format like [\w]+(/[\w]+)*, e.g features/online_help. | |
| * @param string link text, leave it NULL to get link with manual icon | |
| * @param string a word to be displayed after the manual icon (if no icon gets displayed, $title will be used instead!) | |
| * @param integer 1-5: weight of the word. The word will be displayed only if its weight is >= than the user setting threshold. (Default: 1) | |
| * @return string | |
| */ | |
| function get_manual_link( $topic, $link_text = NULL, $action_word = NULL, $word_weight = 1 ) | |
| { | |
| global $online_help_links; | |
| if( $online_help_links ) | |
| { | |
| $manual_url = get_manual_url( $topic ); | |
| if( $link_text == NULL ) | |
| { | |
| if( $action_word == NULL ) | |
| { | |
| $action_word = T_('Manual'); | |
| } | |
| $webhelp_link = action_icon( T_('Open relevant page in online manual'), 'manual', $manual_url, $action_word, 5, $word_weight, array( 'target' => '_blank' ) ); | |
| } | |
| else | |
| { | |
| $webhelp_link = '<a href="'.$manual_url.'" target = "_blank">'.$link_text.'</a>'; | |
| } | |
| return ' '.$webhelp_link; | |
| } | |
| else | |
| { | |
| return ''; | |
| } | |
| } | |
| /** | |
| * Build a string out of $field_attribs, with each attribute | |
| * prefixed by a space character. | |
| * | |
| * @param array Array of field attributes. | |
| * @param boolean Use format_to_output() for the attributes? | |
| * @return string | |
| */ | |
| function get_field_attribs_as_string( $field_attribs, $format_to_output = true ) | |
| { | |
| $r = ''; | |
| foreach( $field_attribs as $l_attr => $l_value ) | |
| { | |
| if( $l_value === NULL ) | |
| { // don't generate empty attributes (it may be NULL if we pass 'value' => NULL as field_param for example, because isset() does not match it!) | |
| // sam2kb> what about alt="" how do we handle this? | |
| // I've removed the "=== ''" check now. Should not do any harm. IIRC NULL is what we want to avoid here. | |
| continue; | |
| } | |
| if( $format_to_output ) | |
| { | |
| $r .= ' '.$l_attr.'="'.format_to_output( $l_value, 'formvalue' ).'"'; | |
| } | |
| else | |
| { | |
| $r .= ' '.$l_attr.'="'.$l_value.'"'; | |
| } | |
| } | |
| return $r; | |
| } | |
| /** | |
| * Update values of HTML tag attributes | |
| * This will only update the first HTML tag at the beginning of the passed param. | |
| * | |
| * @param string HTML tag | |
| * @param array Attributes | |
| * @param array Actions for each attribute: | |
| * 'append' - Append to existing attribute value (Default for all) | |
| * 'skip' - Skip if attribute already exists | |
| * 'replace' - Replace attribute to new value completely | |
| * @return string Updated HTML tag | |
| */ | |
| function update_html_tag_attribs( $html_tag, $new_attribs, $attrib_actions = array() ) | |
| { | |
| // Check for a valid html tag at the beginning of the string: | |
| if( ! preg_match( '#^<([\S]+)[^>]*>#i', $html_tag, $tag_match ) ) | |
| { // Wrong HTML tag format, Return original string: | |
| return $html_tag; | |
| } | |
| $html_tag_name = $tag_match[1]; | |
| // Get the remaining string after the first HTML tag: | |
| $trailing_str = str_replace( $tag_match[0], '', $html_tag ); | |
| $old_attribs = array(); | |
| $updated_attribs = array(); | |
| if( preg_match_all( '@(\S+)=("|\'|)(.*)("|\'|>)@isU', $html_tag, $attr_matches ) ) | |
| { // Get all existing attributes: | |
| foreach( $attr_matches[1] as $o => $old_attr_name ) | |
| { | |
| $old_attribs[ $old_attr_name ] = $attr_matches[3][ $o ]; | |
| if( ! isset( $new_attribs[ $old_attr_name ] ) ) | |
| { // This attribute is not updated, keep current value: | |
| $updated_attribs[] = $old_attr_name.'="'.format_to_output( $attr_matches[3][ $o ], 'formvalue' ).'"'; | |
| } | |
| } | |
| } | |
| foreach( $new_attribs as $new_attrib_name => $new_attrib_value ) | |
| { | |
| if( isset( $old_attribs[ $new_attrib_name ] ) ) | |
| { // If attribute exists in original HTML tag then Update it depending on selected action: | |
| $attrib_action = isset( $attrib_actions[ $new_attrib_name ] ) ? $attrib_actions[ $new_attrib_name ] : 'append'; | |
| switch( $attrib_action ) | |
| { | |
| case 'skip': | |
| // Don't update old value: | |
| $new_attrib_value = $old_attribs[ $new_attrib_name ]; | |
| break; | |
| case 'replace': | |
| // Replace old value with new: | |
| // $new_attrib_value = $new_attrib_value; | |
| break; | |
| case 'append': | |
| default: | |
| // Append new value to old: | |
| $new_attrib_value = $old_attribs[ $new_attrib_name ].' '.$new_attrib_value; | |
| break; | |
| } | |
| } | |
| // ELSE If attribute doesn't exist in original HTML tag then create new one. | |
| $updated_attribs[] = $new_attrib_name.'="'.format_to_output( $new_attrib_value, 'formvalue' ).'"'; | |
| } | |
| return '<'.$html_tag_name.' '.implode( ' ', $updated_attribs ).'>'.$trailing_str; | |
| } | |
| /** | |
| * Is the current page an install page? | |
| * | |
| * @return boolean | |
| */ | |
| function is_install_page() | |
| { | |
| global $is_install_page; | |
| return isset( $is_install_page ) && $is_install_page === true; // check for type also, because of register_globals! | |
| } | |
| /** | |
| * Is the current page an admin/backoffice page? | |
| * | |
| * @return boolean | |
| */ | |
| function is_admin_page() | |
| { | |
| global $is_admin_page; | |
| return isset( $is_admin_page ) && $is_admin_page === true; // check for type also, because of register_globals! | |
| } | |
| /** | |
| * Is the current page a default 'Front' page of a blog? | |
| * | |
| * @return boolean | |
| */ | |
| function is_front_page() | |
| { | |
| global $is_front; | |
| return isset( $is_front ) && $is_front === true; | |
| } | |
| /** | |
| * Is pro version? | |
| * | |
| * @return boolean | |
| */ | |
| function is_pro() | |
| { | |
| global $app_pro; | |
| return isset( $app_pro ) && $app_pro === true; | |
| } | |
| /** | |
| * Check if current version is PRO otherwise redirect to info page about PRO version | |
| */ | |
| function check_pro() | |
| { | |
| if( ! is_pro() ) | |
| { // Restrict the checking feature for NOT PRO version: | |
| global $admin_url; | |
| header_redirect( $admin_url.'?ctrl=pro_only' ); | |
| } | |
| } | |
| /** | |
| * Does the given url require logged in user | |
| * | |
| * @param string url | |
| * @param boolean set true to also check if url is login screen or not | |
| * @return boolean | |
| */ | |
| function require_login( $url, $check_login_screen ) | |
| { | |
| global $Settings, $dispatcher; | |
| if( preg_match( '#/'.preg_quote( $dispatcher, '#' ).'([&?].*)?$#', $url ) ) | |
| { // admin always require logged in user | |
| return true; | |
| } | |
| if( $check_login_screen && preg_match( '#/login.php([&?].*)?$#', $url ) ) | |
| { | |
| return true; | |
| } | |
| $disp_names = 'threads|messages|contacts'; | |
| if( !$Settings->get( 'allow_anonymous_user_list' ) ) | |
| { | |
| $disp_names .= '|users'; | |
| } | |
| if( !$Settings->get( 'allow_anonymous_user_profiles' ) ) | |
| { | |
| $disp_names .= '|user'; | |
| } | |
| if( $check_login_screen ) | |
| { | |
| $disp_names .= '|login'; | |
| } | |
| if( preg_match( '#disp=('.$disp_names.')#', $url ) ) | |
| { // $url require logged in user | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Implode array( 'x', 'y', 'z' ) to something like 'x, y and z'. Useful for displaying list to the end user. | |
| * | |
| * If there's one element in the table, it is returned. | |
| * If there are at least two elements, the last one is concatenated using $implode_last, while the ones before are imploded using $implode_by. | |
| * | |
| * @todo dh> I don't think using entities/HTML as default for $implode_last is sane! | |
| * Use "&" instead and make sure that the output for HTML is HTML compliant.. | |
| * @todo Support for locales that have a different kind of enumeration?! | |
| * @return string | |
| */ | |
| function implode_with_and( $arr, $implode_by = ', ', $implode_last = ' & ' ) | |
| { | |
| switch( count($arr) ) | |
| { | |
| case 0: | |
| return ''; | |
| case 1: | |
| $r = array_shift($arr); | |
| return $r; | |
| default: | |
| $r = implode( $implode_by, array_slice( $arr, 0, -1 ) ) | |
| .$implode_last.array_pop( $arr ); | |
| return $r; | |
| } | |
| } | |
| /** | |
| * Display an array as a list: | |
| * | |
| * @param array | |
| * @param string | |
| * @param string | |
| * @param string | |
| * @param string | |
| * @param string | |
| */ | |
| function display_list( $items, $list_start = '<ul>', $list_end = '</ul>', $item_separator = '', | |
| $item_start = '<li>', $item_end = '</li>', $force_hash = NULL, $max_items = NULL, $link_params = array() ) | |
| { | |
| if( !is_null($max_items) && $max_items < 1 ) | |
| { | |
| return; | |
| } | |
| if( !empty( $items ) ) | |
| { | |
| echo $list_start; | |
| $count = 0; | |
| $first = true; | |
| foreach( $items as $item ) | |
| { // For each list item: | |
| $link = resolve_link_params( $item, $force_hash, $link_params ); | |
| if( empty( $link ) ) | |
| { | |
| continue; | |
| } | |
| $count++; | |
| if( $count>1 ) | |
| { | |
| echo $item_separator; | |
| } | |
| echo $item_start.$link.$item_end; | |
| if( !is_null($max_items) && $count >= $max_items ) | |
| { | |
| break; | |
| } | |
| } | |
| echo $list_end; | |
| } | |
| } | |
| /** | |
| * Credits stuff. | |
| */ | |
| function display_param_link( $params ) | |
| { | |
| echo resolve_link_params( $params ); | |
| } | |
| /** | |
| * Resolve a link based on params (credits stuff) | |
| * | |
| * @param array | |
| * @param integer | |
| * @param array | |
| * @return string | |
| */ | |
| function resolve_link_params( $item, $force_hash = NULL, $params = array() ) | |
| { | |
| global $current_locale; | |
| // echo 'resolve link '; | |
| if( is_array( $item ) ) | |
| { | |
| if( isset( $item[0] ) ) | |
| { // Older format, which displays the same thing for all locales: | |
| return generate_link_from_params( $item, $params ); | |
| } | |
| else | |
| { // First get the right locale: | |
| // echo $current_locale; | |
| foreach( $item as $l_locale => $loc_item ) | |
| { | |
| if( $l_locale == substr( $current_locale, 0, strlen($l_locale) ) ) | |
| { // We found a matching locale: | |
| //echo "[$l_locale/$current_locale]"; | |
| if( is_array( $loc_item[0] ) ) | |
| { // Randomize: | |
| $loc_item = hash_link_params( $loc_item, $force_hash ); | |
| } | |
| return generate_link_from_params( $loc_item, $params ); | |
| } | |
| } | |
| // No match found! | |
| return ''; | |
| } | |
| } | |
| // Super old format: | |
| return $item; | |
| } | |
| /** | |
| * Get a link line, based url hash combined with probability percentage in first column | |
| * | |
| * @param array of arrays | |
| * @param display for a specific hash key | |
| */ | |
| function hash_link_params( $link_array, $force_hash = NULL ) | |
| { | |
| global $ReqHost, $ReqPath, $ReqURI; | |
| static $hash; | |
| if( !is_null($force_hash) ) | |
| { | |
| $hash = $force_hash; | |
| } | |
| elseif( !isset($hash) ) | |
| { | |
| $key = $ReqHost.$ReqPath; | |
| global $Collection, $Blog; | |
| if( !empty($Blog) && strpos( $Blog->get_setting('single_links'), 'param_' ) === 0 ) | |
| { // We are on a blog that doesn't even have clean URLs for posts | |
| $key .= $ReqURI; | |
| } | |
| $hash = 0; | |
| for( $i=0; $i<strlen($key); $i++ ) | |
| { | |
| $hash += ord($key[$i]); | |
| } | |
| $hash = $hash % 100 + 1; | |
| // $hash = rand( 1, 100 ); | |
| global $debug, $Debuglog; | |
| if( $debug ) | |
| { | |
| $Debuglog->add( 'Hash key: '.$hash, 'request' ); | |
| } | |
| } | |
| // echo "[$hash] "; | |
| foreach( $link_array as $link_params ) | |
| { | |
| // echo '<br>'.$hash.'-'.$link_params[ 0 ]; | |
| if( $hash <= $link_params[ 0 ] ) | |
| { // select this link! | |
| // pre_dump( $link_params ); | |
| array_shift( $link_params ); | |
| return $link_params; | |
| } | |
| } | |
| // somehow no match, return 1st element: | |
| $link_params = $link_array[0]; | |
| array_shift( $link_params ); | |
| return $link_params; | |
| } | |
| /** | |
| * Generate a link from params (credits stuff) | |
| * | |
| * @param array | |
| * @param array | |
| */ | |
| function generate_link_from_params( $link_params, $params = array() ) | |
| { | |
| $url = $link_params[0]; | |
| if( empty( $url ) ) | |
| { | |
| return ''; | |
| } | |
| // Make sure we are not missing any param: | |
| $params = array_merge( array( | |
| 'type' => 'link', | |
| 'img_url' => '', | |
| 'img_width' => '', | |
| 'img_height' => '', | |
| 'title' => '', | |
| 'target' => '_blank', | |
| 'rel' => 'noopener', | |
| ), $params ); | |
| $text = $link_params[1]; | |
| if( is_array($text) ) | |
| { | |
| $text = hash_link_params( $text ); | |
| $text = $text[0]; | |
| } | |
| if( empty( $text ) ) | |
| { | |
| return ''; | |
| } | |
| $r = '<a href="'.$url.'"'; | |
| if( !empty($params['target'] ) ) | |
| { | |
| $r .= ' target="'.$params['target'].'"'; | |
| } | |
| if( !empty($params['rel'] ) ) | |
| { | |
| $r .= ' rel="'.$params['rel'].'"'; | |
| } | |
| if( $params['type'] == 'img' ) | |
| { | |
| return $r.' title="'.$params['title'].'"><img src="'.$params['img_url'].'" alt="' | |
| .$text.'" title="'.$params['title'].'" width="'.$params['img_width'].'" height="'.$params['img_height'] | |
| .'" border="0" /></a>'; | |
| } | |
| return $r.'>'.$text.'</a>'; | |
| } | |
| /** | |
| * Send a result as javascript | |
| * automatically includes any Messages ( @see Log::display() ) | |
| * no return from function as it terminates processing | |
| * | |
| * @author Yabba | |
| * | |
| * @todo dh> Move this out into some more specific (not always included) file. | |
| * | |
| * @param array $methods javascript funtions to call with array of parameters | |
| * format : 'function_name' => array( param1, parm2, param3 ) | |
| * @param boolean $send_as_html Wrap the script into an html page with script tag; default is to send as js file | |
| * @param string $target prepended to function calls : blank or window.parent | |
| */ | |
| function send_javascript_message( $methods = array(), $send_as_html = false, $target = '' ) | |
| { | |
| // lets spit out any messages | |
| global $Messages, $param_input_err_messages; | |
| ob_start(); | |
| $Messages->display(); | |
| $output = ob_get_clean(); | |
| // Initialize JavaScript params to send what field should be marked are error | |
| $js_error_params = array(); | |
| if( ! empty( $param_input_err_messages ) && is_array( $param_input_err_messages ) ) | |
| { | |
| foreach( $param_input_err_messages as $param_name => $param_error ) | |
| { | |
| $js_error_params[] = $param_name.': \''.format_to_js( $param_error ).'\''; | |
| } | |
| } | |
| $js_error_params = '{'.implode( ', ', $js_error_params ).'}'; | |
| // set target | |
| $target = ( $target ? $target : param( 'js_target', 'string' ) ); | |
| if( $target ) | |
| { // add trailing [dot] | |
| $target = trim( $target, '.' ).'.'; | |
| } | |
| // target should be empty or window.parent. | |
| if( $target && $target != 'window.parent.' ) | |
| { | |
| debug_die( 'Unexpected javascript target' ); | |
| } | |
| if( $output ) | |
| { // we have some messages | |
| $output = $target.'DisplayServerMessages( \''.format_to_js( $output ).'\', '.$js_error_params.' );'."\n"; | |
| } | |
| if( !empty( $methods ) ) | |
| { // we have a methods to call | |
| foreach( $methods as $method => $param_list ) | |
| { // loop through each requested method | |
| $params = array(); | |
| $internal_scripts = array(); | |
| if( !is_array( $param_list ) ) | |
| { // lets make it an array | |
| $param_list = array( $param_list ); | |
| } | |
| foreach( $param_list as $param ) | |
| { // add each parameter to the output | |
| if( is_array( $param ) ) | |
| { // This is an array: | |
| $param = json_encode( $param ); | |
| } | |
| elseif( !is_numeric( $param ) ) | |
| { // This is a string | |
| if( preg_match_all( '#<script(.*?)?>(.*?)</script>#is', $param, $match_scripts ) ) | |
| { // Extract internal scripts from content: | |
| $param = str_replace( $match_scripts[0], '', $param ); | |
| foreach( $match_scripts[2] as $i => $internal_script_code ) | |
| { | |
| if( $internal_script_code === '' && strpos( $match_scripts[1][ $i ], 'src=' ) !== false ) | |
| { // Append external JS file to <head> to execute code: | |
| $internal_script_code = 'jQuery( \'head\' ).append( \''.format_to_js( $match_scripts[0][ $i ] ).'\' );'; | |
| } | |
| $internal_scripts[] = $internal_script_code; | |
| } | |
| } | |
| // Quote the string to javascript format: | |
| $param = '\''.format_to_js( $param ).'\''; | |
| } | |
| $params[] = $param;// add param to the list | |
| } | |
| // Add method and parameters: | |
| $output .= $target.$method.'('.implode( ',', $params ).');'."\n"; | |
| // Append all internal scripts from content on order to execute this properly: | |
| foreach( $internal_scripts as $internal_script ) | |
| { | |
| $output .= "\n// Internal script from {$target}{$method}():\n".$internal_script; | |
| } | |
| } | |
| } | |
| // Send the predefined cookies: | |
| evo_sendcookies(); | |
| if( $send_as_html ) | |
| { // we want to send as a html document | |
| if( ! headers_sent() ) | |
| { // Send headers only when they are not send yet to avoid an error: | |
| headers_content_mightcache( 'text/html', 0 ); // Do NOT cache interactive communications. | |
| } | |
| echo '<html><head></head><body><script>'."\n"; | |
| echo $output; | |
| echo '</script></body></html>'; | |
| } | |
| else | |
| { // we want to send as js | |
| if( ! headers_sent() ) | |
| { // Send headers only when they are not send yet to avoid an error: | |
| headers_content_mightcache( 'text/javascript', 0 ); // Do NOT cache interactive communications. | |
| } | |
| global $baseurl; | |
| if( empty( $_SERVER['HTTP_REFERER'] ) || | |
| ! ( $referer_url = parse_url( $_SERVER['HTTP_REFERER'] ) ) || empty( $referer_url['host'] ) || | |
| ! ( $baseurl_info = parse_url( $baseurl ) ) || empty( $baseurl_info['host'] ) || | |
| ( $baseurl_info['host'] != $referer_url['host'] ) ) | |
| { // Deny request from other server: | |
| echo 'alert( \''.sprintf( TS_('This action can only be performed with HTTP_REFERER = %s'), $baseurl ).' \');'; | |
| } | |
| else | |
| { // Send JS content only for allowed referer: | |
| echo $output; | |
| } | |
| } | |
| exit(0); | |
| } | |
| /** | |
| * Basic tidy up of strings for using in JavaScript | |
| * | |
| * @author Yabba | |
| * @author Tblue | |
| * | |
| * @param string Unformatted raw data | |
| * @return string Formatted data | |
| */ | |
| function format_to_js( $unformatted ) | |
| { | |
| // Convert the following chars: | |
| // \ => \\ | |
| // ' => \' | |
| // \n => \\n | |
| // newline => \n | |
| // \r => \\r | |
| // carriage return => \r | |
| // \t => \\t | |
| // tab space => \t | |
| return addcslashes( $unformatted, "\\'\n\r\t" ); | |
| } | |
| /** | |
| * Get available cort oprions for items | |
| * | |
| * @param integer|NULL Collection ID to get only enabled item types for the collection, NULL to get all item types | |
| * @param boolean true to enable none option | |
| * @param boolean true to also return custom field options, false otherwise | |
| * @return array key=>name or array( 'general' => array( key=>name ), 'custom' => array( key=>name ) ) | |
| */ | |
| function get_available_sort_options( $coll_ID = NULL, $allow_none = false, $include_custom_fields = false ) | |
| { | |
| $options = array(); | |
| if( $allow_none ) | |
| { // Enable none option | |
| $options[''] = T_('None'); | |
| } | |
| $options = array_merge( $options, array( | |
| 'datestart' => T_('Date issued (Default)'), | |
| 'order' => T_('Order (as explicitly specified)'), | |
| //'datedeadline' => T_('Deadline'), | |
| 'title' => T_('Title'), | |
| 'datecreated' => T_('Date created'), | |
| 'datemodified' => T_('Date last modified'), | |
| 'last_touched_ts' => T_('Date last touched'), | |
| 'contents_last_updated_ts' => T_('Contents last updated'), | |
| 'urltitle' => T_('URL "filename"'), | |
| 'priority' => T_('Priority'), | |
| 'numviews' => T_('Number of members who have viewed the post (If tracking enabled)'), | |
| 'RAND' => T_('Random order!'), | |
| ) ); | |
| if( $include_custom_fields ) | |
| { // We need to include custom fields as well: | |
| global $DB; | |
| $SQL = new SQL( 'Get custom fields for items sort options' ); | |
| $SQL->SELECT( 'DISTINCT( CONCAT( "custom_", itcf_type, "_", itcf_name ) ), itcf_name' ); | |
| $SQL->FROM( 'T_items__type_custom_field' ); | |
| $SQL->FROM_add( 'INNER JOIN T_items__type ON ityp_ID = itcf_ityp_ID' ); | |
| $SQL->WHERE( 'ityp_usage = "post"' ); | |
| if( ! empty( $coll_ID ) ) | |
| { // Restrict by enabled item types for the given collection: | |
| $SQL->FROM_add( 'INNER JOIN T_items__type_coll ON itc_ityp_ID = ityp_ID' ); | |
| $SQL->WHERE_and( 'itc_coll_ID = '.$DB->quote( $coll_ID ) ); | |
| } | |
| $custom_fields = $DB->get_assoc( $SQL ); | |
| // Add custom fields as available sort options: | |
| return array( 'general' => $options, 'custom' => $custom_fields ); | |
| } | |
| return $options; | |
| } | |
| /** | |
| * Get available sort options for blogs | |
| * | |
| * @return array key=>name | |
| */ | |
| function get_coll_sort_options() | |
| { | |
| return array( | |
| 'order' => T_('Order (Default)'), | |
| 'ID' => T_('Blog ID'), | |
| 'name' => T_('Name'), | |
| 'shortname' => T_('Short name'), | |
| 'tagline' => T_('Tagline'), | |
| 'shortdesc' => T_('Short Description'), | |
| 'urlname' => T_('URL "filename"'), | |
| 'RAND' => T_('Random order!'), | |
| ); | |
| } | |
| /** | |
| * Converts array to form option list | |
| * | |
| * @param array of option values and descriptions | |
| * @param integer|array selected keys | |
| * @param array provide a choice for "none_value" with value '' | |
| * @return string | |
| */ | |
| function array_to_option_list( $array, $default = '', $allow_none = array() ) | |
| { | |
| if( !is_array( $default ) ) | |
| { | |
| $default = array( $default ); | |
| } | |
| $r = ''; | |
| if( !empty($allow_none) ) | |
| { | |
| $r .= '<option value="'.$allow_none['none_value'].'"'; | |
| if( empty($default) ) $r .= ' selected="selected"'; | |
| $r .= '>'.format_to_output($allow_none['none_text']).'</option>'."\n"; | |
| } | |
| foreach( $array as $k=>$v ) | |
| { | |
| $r .= '<option value="'.format_to_output($k,'formvalue').'"'; | |
| if( in_array( $k, $default ) ) $r .= ' selected="selected"'; | |
| $r .= '>'; | |
| $r .= format_to_output( $v, 'htmlbody' ); | |
| $r .= '</option>'."\n"; | |
| } | |
| return $r; | |
| } | |
| /** | |
| * Get a value from a volatile/lossy cache. | |
| * | |
| * @param string key | |
| * @param boolean success (by reference) | |
| * @return mixed True in case of success, false in case of failure. NULL, if no backend is available. | |
| */ | |
| function get_from_mem_cache( $key, & $success ) | |
| { | |
| global $Timer; | |
| $Timer->resume( 'get_from_mem_cache', false ); | |
| if( function_exists( 'apcu_fetch' ) ) | |
| { // APCu | |
| $r = apcu_fetch( $key, $success ); | |
| } | |
| elseif( function_exists( 'apc_fetch' ) ) | |
| { // APC | |
| $r = apc_fetch( $key, $success ); | |
| } | |
| elseif( function_exists( 'xcache_get' ) && ini_get( 'xcache.var_size' ) > 0 ) | |
| { // XCache | |
| $r = xcache_get( $key ); | |
| } | |
| if( ! isset($success) ) | |
| { // set $success for implementation that do not set it itself (only APC does so) | |
| $success = isset($r); | |
| } | |
| if( ! $success ) | |
| { | |
| $r = NULL; | |
| global $Debuglog; | |
| $Debuglog->add( 'No caching backend available for reading "'.$key.'".', 'cache' ); | |
| } | |
| $Timer->pause( 'get_from_mem_cache', false ); | |
| return $r; | |
| } | |
| /** | |
| * Set a value to a volatile/lossy cache. | |
| * | |
| * There's no guarantee that the data is still available, since e.g. old | |
| * values might get purged. | |
| * | |
| * @param string key | |
| * @param mixed Data. Objects would have to be serialized. | |
| * @param int Time to live (seconds). Default is 0 and means "forever". | |
| * @return mixed | |
| */ | |
| function set_to_mem_cache( $key, $payload, $ttl = 0 ) | |
| { | |
| global $Timer; | |
| $Timer->resume( 'set_to_mem_cache', false ); | |
| if( function_exists( 'apcu_store' ) ) | |
| { // APCu | |
| $r = apcu_store( $key, $payload, $ttl ); | |
| } | |
| elseif( function_exists( 'apc_store' ) ) | |
| { // APC | |
| $r = apc_store( $key, $payload, $ttl ); | |
| } | |
| elseif( function_exists( 'xcache_set' ) && ini_get( 'xcache.var_size' ) > 0 ) | |
| { // XCache | |
| $r = xcache_set( $key, $payload, $ttl ); | |
| } | |
| else | |
| { // No available cache module: | |
| global $Debuglog; | |
| $Debuglog->add( 'No caching backend available for writing "'.$key.'".', 'cache' ); | |
| $r = NULL; | |
| } | |
| $Timer->pause( 'set_to_mem_cache', false ); | |
| return $r; | |
| } | |
| /** | |
| * Remove a given key from the volatile/lossy cache. | |
| * | |
| * @param string key | |
| * @return boolean True on success, false on failure. NULL if no backend available. | |
| */ | |
| function unset_from_mem_cache( $key ) | |
| { | |
| if( function_exists( 'apc_delete' ) ) | |
| { // APC | |
| return apc_delete( $key ); | |
| } | |
| if( function_exists( 'xcache_unset' ) ) | |
| { // XCache | |
| return xcache_unset( gen_key_for_cache( $key ) ); | |
| } | |
| if( function_exists( 'apcu_delete' ) ) | |
| { // APCu | |
| return apcu_delete( $key ); | |
| } | |
| } | |
| /** | |
| * Generate order by clause | |
| * | |
| * @param string The order values are separated by space or comma | |
| * @param string An order direction: ASC, DESC | |
| * @param string DB prefix | |
| * @param string ID field name with prefix | |
| * @param array Names of DB fields(without prefix) that are available | |
| * @return string The order fields are separated by comma | |
| */ | |
| function gen_order_clause( $order_by, $order_dir, $dbprefix, $dbIDname_disambiguation, $available_fields = NULL ) | |
| { | |
| $order_by = str_replace( ' ', ',', $order_by ); | |
| $orderby_array = explode( ',', $order_by ); | |
| $order_dir = explode( ',', str_replace( ' ', ',', $order_dir ) ); | |
| if( is_array( $available_fields ) ) | |
| { // Exclude the incorrect fields from order clause | |
| foreach( $orderby_array as $i => $orderby_field ) | |
| { | |
| if( ! in_array( $orderby_field, $available_fields ) ) | |
| { | |
| unset( $orderby_array[ $i ] ); | |
| } | |
| } | |
| } | |
| // Format each order param with default column names: | |
| foreach( $orderby_array as $i => $orderby_value ) | |
| { // If the order_by field contains a '.' character which is a table separator we must not use the prefix ( E.g. temp_table.value ) | |
| $use_dbprefix = ( strpos( $orderby_value, '.' ) !== false ) ? '' : $dbprefix; | |
| $orderby_array[ $i ] = $use_dbprefix.$orderby_value.' '.( isset( $order_dir[ $i ] ) ? $order_dir[ $i ] : $order_dir[0] ); | |
| } | |
| // Add an ID parameter to make sure there is no ambiguity in ordering on similar items: | |
| $orderby_array[] = $dbIDname_disambiguation.' '.$order_dir[0]; | |
| $order_by = implode( ', ', $orderby_array ); | |
| // Special case for RAND: | |
| $order_by = str_replace( $dbprefix.'RAND ', 'RAND() ', $order_by ); | |
| return $order_by; | |
| } | |
| /** | |
| * Get the IconLegend instance. | |
| * | |
| * @return IconLegend or false, if the user has not set "display_icon_legend" | |
| */ | |
| function & get_IconLegend() | |
| { | |
| static $IconLegend; | |
| if( ! isset($IconLegend) ) | |
| { | |
| global $UserSettings; | |
| if( $UserSettings->get('display_icon_legend') ) | |
| { | |
| /** | |
| * Icon Legend | |
| */ | |
| load_class( '_core/ui/_iconlegend.class.php', 'IconLegend' ); | |
| $IconLegend = new IconLegend(); | |
| } | |
| else | |
| { | |
| $IconLegend = false; | |
| } | |
| } | |
| return $IconLegend; | |
| } | |
| /** | |
| * Get name of active opcode cache, or "none". | |
| * {@internal Anyone using something else, please extend.}} | |
| * @return string | |
| */ | |
| function get_active_opcode_cache() | |
| { | |
| if( function_exists('opcache_invalidate') ) | |
| { | |
| return 'OPCache'; | |
| } | |
| if( function_exists('apc_delete_file') ) | |
| { | |
| return 'APC'; | |
| } | |
| // xcache: xcache.var_size must be > 0. xcache_set is not necessary (might have been disabled). | |
| if( ini_get('xcache.size') > 0 ) | |
| { | |
| return 'xcache'; | |
| } | |
| if( ini_get('eaccelerator.enable') ) | |
| { | |
| $eac_info = eaccelerator_info(); | |
| if( $eac_info['cache'] ) | |
| { | |
| return 'eAccelerator'; | |
| } | |
| } | |
| return 'none'; | |
| } | |
| /** | |
| * Get name of active user cache, or "none". | |
| * {@internal Anyone using something else, please extend.}} | |
| * @return string | |
| */ | |
| function get_active_user_cache() | |
| { | |
| if( function_exists( 'apcu_cache_info' ) /* && ini_get( 'apc.enabled' ) */ ) | |
| { | |
| return 'APCu'; | |
| } | |
| if( function_exists( 'apc_cache_info' ) /* && ini_get( 'apc.enabled' ) */ ) | |
| { | |
| return 'APC'; | |
| } | |
| // xcache: xcache.var_size must be > 0. xcache_set is not necessary (might have been disabled). | |
| if( ini_get('xcache.size') > 0 ) | |
| { | |
| return 'xcache'; | |
| } | |
| return 'none'; | |
| } | |
| /** | |
| * Invalidate all page caches. | |
| * This function should be processed every time, when some users or global settings was modified, | |
| * and this modification has an imortant influence for the front office display. | |
| * Modifications that requires to invalidate all page caches: | |
| * - installing/removing/reloading/enabling/disabling plugins | |
| * - editing user settings like allow profile pics, new users can register, user settings>display | |
| */ | |
| function invalidate_pagecaches() | |
| { | |
| global $DB, $Settings, $servertimenow; | |
| // get current server time | |
| $timestamp = ( empty( $servertimenow ) ? time() : $servertimenow ); | |
| // get all blog ids | |
| if( $blog_ids = $DB->get_col( 'SELECT blog_ID FROM T_blogs' ) ) | |
| { // build invalidate query | |
| $query = 'REPLACE INTO T_coll_settings ( cset_coll_ID, cset_name, cset_value ) VALUES'; | |
| foreach( $blog_ids as $blog_id ) | |
| { | |
| $query .= ' ('.$blog_id.', "last_invalidation_timestamp", '.$timestamp.' ),'; | |
| } | |
| $query = substr( $query, 0, strlen( $query ) - 1 ); | |
| $DB->query( $query, 'Invalidate blogs\'s page caches' ); | |
| } | |
| // Invalidate general cache content also | |
| $Settings->set( 'last_invalidation_timestamp', $timestamp ); | |
| $Settings->dbupdate(); | |
| } | |
| /** | |
| * Get $ReqPath, $ReqURI | |
| * | |
| * @return array ($ReqPath,$ReqURI); | |
| */ | |
| function get_ReqURI() | |
| { | |
| global $Debuglog; | |
| // Investigation for following code by Isaac - http://isaacschlueter.com/ | |
| if( isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI']) ) | |
| { // Warning: on some IIS installs it it set but empty! | |
| $Debuglog->add( 'vars: vars: Getting ReqURI from REQUEST_URI', 'request' ); | |
| $ReqURI = $_SERVER['REQUEST_URI']; | |
| // Build requested Path without query string: | |
| $pos = strpos( $ReqURI, '?' ); | |
| if( false !== $pos ) | |
| { | |
| $ReqPath = substr( $ReqURI, 0, $pos ); | |
| } | |
| else | |
| { | |
| $ReqPath = $ReqURI; | |
| } | |
| } | |
| elseif( isset($_SERVER['URL']) ) | |
| { // ISAPI | |
| $Debuglog->add( 'vars: Getting ReqPath from URL', 'request' ); | |
| $ReqPath = $_SERVER['URL']; | |
| $ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath; | |
| } | |
| elseif( isset($_SERVER['PATH_INFO']) ) | |
| { // CGI/FastCGI | |
| if( isset($_SERVER['SCRIPT_NAME']) ) | |
| { | |
| $Debuglog->add( 'vars: Getting ReqPath from PATH_INFO and SCRIPT_NAME', 'request' ); | |
| if ($_SERVER['SCRIPT_NAME'] == $_SERVER['PATH_INFO'] ) | |
| { /* both the same so just use one of them | |
| * this happens on a windoze 2003 box | |
| * gotta love microdoft | |
| */ | |
| $Debuglog->add( 'vars: PATH_INFO and SCRIPT_NAME are the same', 'request' ); | |
| $Debuglog->add( 'vars: Getting ReqPath from PATH_INFO only instead', 'request' ); | |
| $ReqPath = $_SERVER['PATH_INFO']; | |
| } | |
| else | |
| { | |
| $ReqPath = $_SERVER['SCRIPT_NAME'].$_SERVER['PATH_INFO']; | |
| } | |
| } | |
| else | |
| { // does this happen?? | |
| $Debuglog->add( 'vars: Getting ReqPath from PATH_INFO only!', 'request' ); | |
| $ReqPath = $_SERVER['PATH_INFO']; | |
| } | |
| $ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath; | |
| } | |
| elseif( isset($_SERVER['ORIG_PATH_INFO']) ) | |
| { // Tomcat 5.5.x with Herbelin PHP servlet and PHP 5.1 | |
| $Debuglog->add( 'vars: Getting ReqPath from ORIG_PATH_INFO', 'request' ); | |
| $ReqPath = $_SERVER['ORIG_PATH_INFO']; | |
| $ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath; | |
| } | |
| elseif( isset($_SERVER['SCRIPT_NAME']) ) | |
| { // Some Odd Win2k Stuff | |
| $Debuglog->add( 'vars: Getting ReqPath from SCRIPT_NAME', 'request' ); | |
| $ReqPath = $_SERVER['SCRIPT_NAME']; | |
| $ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath; | |
| } | |
| elseif( isset($_SERVER['PHP_SELF']) ) | |
| { // The Old Stand-By | |
| $Debuglog->add( 'vars: Getting ReqPath from PHP_SELF', 'request' ); | |
| $ReqPath = $_SERVER['PHP_SELF']; | |
| $ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath; | |
| } | |
| else | |
| { | |
| $ReqPath = false; | |
| $ReqURI = false; | |
| ?> | |
| <p class="error"> | |
| Warning: $ReqPath could not be set. Probably an odd IIS problem. | |
| </p> | |
| <p> | |
| Go to your <a href="<?php echo $baseurl.$install_subdir ?>phpinfo.php">phpinfo page</a>, | |
| look for occurences of <code><?php | |
| // take the baseurlroot out.. | |
| echo preg_replace('#^'.preg_quote( $baseurlroot, '#' ).'#', '', $baseurl.$install_subdir ) | |
| ?>phpinfo.php</code> and copy all lines | |
| containing this to the <a href="http://forums.b2evolution.net">forum</a>. Also specify what webserver | |
| you're running on. | |
| <br /> | |
| (If you have deleted your install folder – what is recommended after successful setup – | |
| you have to upload it again before doing this). | |
| </p> | |
| <?php | |
| } | |
| $r = array( $ReqPath, $ReqURI ); | |
| // Format several danger chars to urlencoded format to avoid issues: | |
| $r = str_replace( array( '"', '\'', '<', '>' ), array( '%22', '%27', '%3C', '%3E' ), $r ); | |
| return $r; | |
| } | |
| /** | |
| * Get URL to REST API script depending on current collection base url from front-office or site base url from back-office | |
| * | |
| * Note: For back-office or no collection page _init_hit.inc.php should be called before this call, because ReqHost and ReqPath must be initialized | |
| * | |
| * @return string URL to htsrv folder | |
| */ | |
| function get_restapi_url() | |
| { | |
| global $restapi_script; | |
| return get_htsrv_url().$restapi_script; | |
| } | |
| /** | |
| * Force URL from http to https protocol | |
| * | |
| * @param string Original URL | |
| * @param boolean|string TRUE to force without settings checking, | |
| * FALSE to keep original URL without forcing, | |
| * 'login' - Force only when it is enabled by setting "Require SSL" | |
| * @return string Forced URL | |
| */ | |
| function force_https_url( $url, $force_https = true ) | |
| { | |
| if( $force_https === 'login' ) | |
| { // Force url to use https if it is defiend in the setting "Require SSL": | |
| global $Settings; | |
| $force_https = (boolean)$Settings->get( 'require_ssl' ); | |
| } | |
| if( $force_https === true ) | |
| { // Force URL only when it is requested by param or enabled by setting checking above: | |
| $url = preg_replace( '#^http://#i', 'https://', $url ); | |
| } | |
| return $url; | |
| } | |
| /** | |
| * Check and redirect if the requested URL must be used as https instead of http | |
| * | |
| * @param boolean|string TRUE to force without settings checking, | |
| * FALSE to keep original URL without forcing, | |
| * 'login' - Force only when it is enabled by setting "Require SSL" | |
| * @param string Original URL, NULL - to use current URL = $ReqURL | |
| */ | |
| function check_https_url( $force_https = true, $url = NULL ) | |
| { | |
| if( $url === NULL ) | |
| { // Use current URL by default: | |
| global $ReqURL; | |
| if( empty( $ReqURL ) ) | |
| { // If this URL is not defined yet: | |
| return; | |
| } | |
| $url = $ReqURL; | |
| } | |
| // Try to force the requested URL: | |
| $forced_url = force_https_url( $url, $force_https ); | |
| if( $forced_url != $url ) | |
| { // If the requested is wrong then redirect to correct what must be used instead: | |
| header_redirect( $forced_url ); | |
| } | |
| } | |
| /** | |
| * Get URL to htsrv folder depending on current collection base url from front-office or site base url from back-office | |
| * | |
| * Note: For back-office or no collection page _init_hit.inc.php should be called before this call, because ReqHost and ReqPath must be initialized | |
| * | |
| * @param boolean TRUE to use https URL | |
| * @return string URL to htsrv folder | |
| */ | |
| function get_htsrv_url( $force_https = false ) | |
| { | |
| global $Collection, $Blog; | |
| if( is_admin_page() || empty( $Blog ) ) | |
| { // For back-office or when no collection page: | |
| return get_samedomain_htsrv_url( $force_https ); | |
| } | |
| else | |
| { // For current collection: | |
| return $Blog->get_local_htsrv_url( NULL, $force_https ); | |
| } | |
| } | |
| /** | |
| * Get htsrv url on the same domain as the http request came from | |
| * | |
| * Note: _init_hit.inc.php should be called before this call, because ReqHost and ReqPath must be initialized | |
| * | |
| * @param boolean|string TRUE to use https URL | |
| * @return string URL to htsrv folder | |
| */ | |
| function get_samedomain_htsrv_url( $force_https = false ) | |
| { | |
| global $ReqHost, $ReqPath, $baseurl, $htsrv_url, $htsrv_subdir, $Collection, $Blog, $is_cli; | |
| // Force URL if it is required and enabled by settings: | |
| $req_htsrv_url = force_https_url( $htsrv_url, $force_https ); | |
| // Cut htsrv folder from end of the URL: | |
| $req_htsrv_url = substr( $req_htsrv_url, 0, strlen( $req_htsrv_url ) - strlen( $htsrv_subdir ) ); | |
| if( $is_cli || empty( $ReqHost ) || strpos( $ReqHost.$ReqPath, $req_htsrv_url ) !== false ) | |
| { // If current request path contains the required htsrv URL | |
| // or $ReqHost is not initialized e-g on install | |
| // or this is CLI mode where $ReqHost is not defined: | |
| return $req_htsrv_url.$htsrv_subdir; | |
| } | |
| $baseurl_parts = @parse_url( $baseurl ); | |
| $req_url_parts = @parse_url( $ReqHost ); | |
| $htsrv_url_parts = @parse_url( $req_htsrv_url ); | |
| if( ( ! isset( $baseurl_parts['host'] ) ) || | |
| ( ! isset( $req_url_parts['host'] ) ) || | |
| ( ! isset( $htsrv_url_parts['host'] ) ) ) | |
| { | |
| debug_die( 'Invalid hosts!' ); | |
| } | |
| if( $req_url_parts['host'] == $baseurl_parts['host'] && | |
| ! isset( $req_url_parts['path'] ) && | |
| isset( $baseurl_parts['path'] ) ) | |
| { // Don't miss folder of base url from url like http://site.com/folder/: | |
| $req_url_parts['path'] = $baseurl_parts['path']; | |
| } | |
| $req_domain = rtrim( $req_url_parts['host'].( isset( $req_url_parts['path'] ) ? $req_url_parts['path'] : '' ), '/' ); | |
| $htsrv_domain = rtrim( $htsrv_url_parts['host'].( isset( $htsrv_url_parts['path'] ) ? $htsrv_url_parts['path'] : '' ), '/' ); | |
| // Replace domain + path of htsrv URL with current request: | |
| $samedomain_htsrv_url = substr_replace( $req_htsrv_url, $req_domain, strpos( $req_htsrv_url, $htsrv_domain ), strlen( $htsrv_domain ) ); | |
| // Revert htsrv folder to end of the URL which has been cut above: | |
| $samedomain_htsrv_url .= $htsrv_subdir; | |
| // fp> The following check would apply well if we always had 301 redirects. | |
| // But it's possible to turn them off in SEO settings for some page and not others (we don't know which here) | |
| // And some kinds of pages do not have 301 redirections implemented yet, e-g: disp=users | |
| /* | |
| if( ( !is_admin_page() ) && ( !empty( $Blog ) ) && ( $samedomain_htsrv_url != $Blog->get_htsrv_url( $secure ) ) ) | |
| { | |
| debug_die( 'The blog is configured to have /htsrv/ at:<br> '.$Blog->get_htsrv_url( $secure ).'<br>but in order to stay on the current domain, we would need to use:<br>'.$samedomain_htsrv_url.'<br>Maybe we have a missing redirection to the proper blog url?' ); | |
| } | |
| */ | |
| return $samedomain_htsrv_url; | |
| } | |
| /** | |
| * Set max execution time | |
| * | |
| * @param integer seconds | |
| * @return string the old value on success, false on failure. | |
| */ | |
| function set_max_execution_time( $seconds ) | |
| { | |
| if( function_exists( 'set_time_limit' ) ) | |
| { | |
| set_time_limit( $seconds ); | |
| } | |
| return @ini_set( 'max_execution_time', $seconds ); | |
| } | |
| /** | |
| * Sanitize a comma-separated list of numbers (IDs) | |
| * | |
| * @param string | |
| * @param bool Return array if true, string otherwise | |
| * @param bool Quote each element (for use in SQL queries) | |
| * @return string | |
| */ | |
| function sanitize_id_list( $str, $return_array = false, $quote = false ) | |
| { | |
| if( is_null($str) ) | |
| { // Allow NULL values | |
| $str = ''; | |
| } | |
| // Explode and trim | |
| $array = array_map( 'trim', explode(',', $str) ); | |
| // Convert to integer and remove all empty values | |
| $array = array_filter( array_map('intval', $array) ); | |
| if( !$return_array && $quote ) | |
| { // Quote each element and return a string | |
| global $DB; | |
| return $DB->quote($array); | |
| } | |
| return ( $return_array ? $array : implode(',', $array) ); | |
| } | |
| /** | |
| * A wrapper for json_encode function | |
| * We need to pass valid UTF-8 string to json_encode, otherwise it may return NULL | |
| * | |
| * @param mixed | |
| * @return string | |
| */ | |
| function evo_json_encode( $a = false ) | |
| { | |
| if( is_string( $a ) ) | |
| { // Convert to UTF-8 | |
| $a = current_charset_to_utf8( $a ); | |
| } | |
| elseif( is_array( $a ) ) | |
| { // Recursively convert to UTF-8 | |
| array_walk_recursive( $a, 'current_charset_to_utf8' ); | |
| } | |
| $result = json_encode( $a ); | |
| if( $result === false ) | |
| { // If json_encode returns FALSE because of some error we should set correct json empty value as '[]' instead of false | |
| $result = '[]'; | |
| } | |
| return $result; | |
| } | |
| /** | |
| * A helper function to conditionally convert a string from current charset to UTF-8 | |
| * | |
| * @param string | |
| * @return string | |
| */ | |
| function current_charset_to_utf8( & $a ) | |
| { | |
| global $current_charset; | |
| if( is_string( $a ) && $current_charset != '' && $current_charset != 'utf-8' ) | |
| { // Convert string to utf-8 if it has another charset | |
| $a = convert_charset( $a, 'utf-8', $current_charset ); | |
| } | |
| return $a; | |
| } | |
| if( !function_exists( 'property_exists' ) ) | |
| { | |
| /** | |
| * Create property_exists function if it does not exist ( PHP < 5.1 ) | |
| * @param object | |
| * @param string | |
| * | |
| * @return bool | |
| */ | |
| function property_exists( $class, $property ) | |
| { | |
| if( is_object( $class ) ) | |
| { | |
| $vars = get_object_vars( $class ); | |
| } | |
| else | |
| { | |
| $vars = get_class_vars( $class ); | |
| } | |
| return array_key_exists( $property, $vars ); | |
| } | |
| } | |
| // fp>vitaliy: move to a file that is not included everywhere! | |
| /** | |
| * Update global $http_response_code and call function header() | |
| * | |
| * NOTICE: When you start to use new code please add it to the hits filter "HTTP resp" | |
| * in the file "/inc/sessions/views/_stats_view.funcs.php", | |
| * function filter_hits(), array $resp_codes | |
| * | |
| * @param string Header | |
| * @param integer Header response code | |
| */ | |
| function header_http_response( $string, $code = NULL ) | |
| { | |
| global $http_response_code; | |
| $string = 'HTTP/1.1 '. $string; | |
| if( is_null( $code ) ) | |
| { | |
| if( preg_match( '/(\d{3})/', $string, $matches ) ) | |
| { | |
| $http_response_code = (int)$matches[0]; | |
| } | |
| } | |
| else | |
| { | |
| $http_response_code = $code; | |
| } | |
| header( $string ); | |
| } | |
| /** | |
| * Add a trailing slash, if none present | |
| * | |
| * @param string the path/url | |
| * @return string the path/url with trailing slash | |
| */ | |
| function trailing_slash( $path ) | |
| { | |
| if( empty($path) || utf8_substr( $path, -1 ) == '/' ) | |
| { | |
| return $path; | |
| } | |
| else | |
| { | |
| return $path.'/'; | |
| } | |
| } | |
| /** | |
| * Remove trailing slash, if present | |
| * | |
| * @param string the path/url | |
| * @return string the path/url without trailing slash | |
| */ | |
| function no_trailing_slash( $path ) | |
| { | |
| if( utf8_substr( $path, -1 ) == '/' ) | |
| { | |
| return utf8_substr( $path, 0, utf8_strlen( $path )-1 ); | |
| } | |
| else | |
| { | |
| return $path; | |
| } | |
| } | |
| /** | |
| * Convert integer to IP address | |
| * | |
| * @param integer Number | |
| * @return string IP address | |
| */ | |
| function int2ip( $int ) | |
| { | |
| $ip = array(); | |
| $ip[0] = (int) ( $int / 256 / 256 / 256 ); | |
| $ip[1] = (int) ( ( $int - ( $ip[0] * 256 * 256 * 256 ) ) / 256 / 256 ); | |
| $ip[2] = (int) ( ( $int - ( $ip[0] * 256 * 256 * 256 ) - ( $ip[1] * 256 * 256 ) ) / 256 ); | |
| $ip[3] = $int - ( $ip[0] * 256 * 256 * 256 ) - ( $ip[1] * 256 * 256 ) - ( $ip[2] * 256 ); | |
| return $ip[0].'.'.$ip[1].'.'.$ip[2].'.'.$ip[3]; | |
| } | |
| /** | |
| * Check if the given string is a valid IPv4 or IPv6 address value | |
| * | |
| * @param string IP | |
| * @return boolean true if valid, false otherwise | |
| */ | |
| function is_valid_ip_format( $ip ) | |
| { | |
| return filter_var( $ip, FILTER_VALIDATE_IP ) !== false; | |
| } | |
| /** | |
| * Convert IP address to integer (get only 32bits of IPv6 address) | |
| * | |
| * @param string IP address | |
| * @return integer Number | |
| */ | |
| function ip2int( $ip ) | |
| { | |
| if( ! is_valid_ip_format( $ip ) ) | |
| { // IP format is incorrect | |
| return 0; | |
| } | |
| if( $ip == '::1' ) | |
| { // Reserved IP for localhost | |
| $ip = '127.0.0.1'; | |
| } | |
| $parts = unpack( 'N*', inet_pton( $ip ) ); | |
| // In case of IPv6 return only a parts of it | |
| $result = ( strpos( $ip, '.' ) !== false ) ? $parts[1] /* IPv4*/ : $parts[4] /* IPv6*/; | |
| if( $result < 0 ) | |
| { // convert unsigned int to signed from unpack. | |
| // this should be OK as it will be a PHP float not an int | |
| $result += 4294967296; | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Check if URL has a domain in IP format | |
| * | |
| * @param string URL | |
| * @return boolean | |
| */ | |
| function is_ip_url_domain( $url ) | |
| { | |
| $url_data = parse_url( $url ); | |
| if( $url_data === false || ! isset( $url_data['host'] ) ) | |
| { // Wrong url: | |
| return false; | |
| } | |
| // Check if host is IP address: | |
| return is_valid_ip_format( $url_data['host'] ); | |
| } | |
| /** | |
| * Save text data to file, create target file if it doesn't exist | |
| * | |
| * @param string data to be written | |
| * @param string filename (full path to a file) | |
| * @param string fopen mode | |
| */ | |
| function save_to_file( $data, $filename, $mode = 'a' ) | |
| { | |
| global $Settings, $evo_save_file_error_msg; | |
| if( ! file_exists( $filename ) ) | |
| { // Try to create a target file: | |
| if( ! @touch( $filename ) ) | |
| { // If file could not be created: | |
| $evo_save_file_error_msg = T_('File could not be created!'); | |
| return false; | |
| } | |
| // Doesn't work during installation | |
| if( ! empty( $Settings ) ) | |
| { | |
| $chmod = $Settings->get( 'fm_default_chmod_file' ); | |
| @chmod( $filename, octdec( $chmod ) ); | |
| } | |
| } | |
| if( ! is_writable( $filename ) ) | |
| { // File is not writable: | |
| $evo_save_file_error_msg = T_('File is not writable!'); | |
| return false; | |
| } | |
| if( ! ( $f = @fopen( $filename, $mode ) ) ) | |
| { // Could not open file: | |
| $evo_save_file_error_msg = T_('File could not be opened for writing data!'); | |
| return false; | |
| } | |
| if( ! @fwrite( $f, $data ) ) | |
| { // Could not write data into file: | |
| $evo_save_file_error_msg = T_('Data could not be written to the file!'); | |
| return false; | |
| } | |
| @fclose( $f ); | |
| if( ! file_exists( $filename ) ) | |
| { // Additonal check for existing file on disk: | |
| $evo_save_file_error_msg = T_('File doesn\'t exist!'); | |
| return false; | |
| } | |
| // Reset error log on success result: | |
| $evo_save_file_error_msg = ''; | |
| // Return file name on success result: | |
| return $filename; | |
| } | |
| /** | |
| * Check if current request is AJAX | |
| * | |
| * @return boolean TRUE/FALSE | |
| */ | |
| function is_ajax_request() | |
| { | |
| global $is_ajax_request; | |
| return isset( $is_ajax_request ) && $is_ajax_request === true; | |
| } | |
| /** | |
| * Check if current request is AJAX content | |
| * Used in order to get only content of the requested page | |
| * | |
| * @param string Template name | |
| * @return boolean TRUE/FALSE | |
| */ | |
| function is_ajax_content( $template_name = '' ) | |
| { | |
| global $ajax_content_mode; | |
| // Template names of content: @see skin_include() | |
| $content_templates = array( '$disp$', '_item_block.inc.php', '_item_content.inc.php' ); | |
| return !empty( $ajax_content_mode ) && | |
| $ajax_content_mode === true && | |
| !in_array( $template_name, $content_templates ); | |
| } | |
| /** | |
| * Add a message to AJAX Log | |
| * | |
| * @param string Message | |
| * @param string|array Category, default is to use the object's default category. | |
| * Can also be an array of categories to add the same message to. | |
| */ | |
| function ajax_log_add( $message, $category = NULL ) | |
| { | |
| global $ajax_Log; | |
| if( ! isset( $ajax_Log ) || | |
| ! ( $ajax_Log instanceof Log ) ) | |
| { // AJAX Log is not initialized: | |
| return; | |
| } | |
| // Add a message to AJAX Log: | |
| $ajax_Log->add( $message, $category ); | |
| } | |
| /** | |
| * Display AJAX log | |
| */ | |
| function ajax_log_display() | |
| { | |
| global $ajax_Log, $debug, $debug_jslog, $current_debug, $current_debug_jslog; | |
| if( ! ( $debug || $debug_jslog || $current_debug || $current_debug_jslog ) ) | |
| { // At least one debug must be enabled: | |
| return; | |
| } | |
| if( ! isset( $ajax_Log ) || | |
| ! ( $ajax_Log instanceof Log ) ) | |
| { // AJAX Log is not initialized: | |
| return; | |
| } | |
| // Print out AJAX Log messages: | |
| $ajax_Log->display( NULL, NULL, true, 'all', | |
| array( | |
| 'error' => array( 'class' => 'jslog_error', 'divClass' => false ), | |
| 'note' => array( 'class' => 'jslog_note', 'divClass' => false ), | |
| ), 'ul', 'jslog' ); | |
| } | |
| /** | |
| * Insert system log into DB | |
| * | |
| * @param string Message text | |
| * @param string Log type: 'info', 'warning', 'error', 'critical_error' | |
| * @param string Object type: 'comment', 'item', 'user', 'file', 'email_log' or leave default NULL if none of them | |
| * @param integer Object ID | |
| * @param string Origin type: 'core', 'plugin' | |
| * @param integer Origin ID | |
| * @param integer User ID | |
| */ | |
| function syslog_insert( $message, $log_type, $object_type = NULL, $object_ID = NULL, $origin_type = 'core', $origin_ID = NULL, $user_ID = NULL ) | |
| { | |
| global $servertimenow; | |
| $Syslog = new Syslog(); | |
| $Syslog->set_user( $user_ID ); | |
| $Syslog->set( 'type', $log_type ); | |
| $Syslog->set_origin( $origin_type, $origin_ID ); | |
| $Syslog->set_object( $object_type, $object_ID ); | |
| $Syslog->set_message( $message ); | |
| $Syslog->set( 'timestamp', date2mysql( $servertimenow ) ); | |
| $Syslog->dbinsert(); | |
| } | |
| /** | |
| * Get a param to know where script is calling now, Used for JS functions | |
| * | |
| * @return string | |
| */ | |
| function request_from() | |
| { | |
| global $request_from; | |
| if( !empty( $request_from ) ) | |
| { // AJAX request | |
| return $request_from; | |
| } | |
| if( is_admin_page() ) | |
| { // Backoffice | |
| global $ctrl; | |
| return !empty( $ctrl ) ? $ctrl : 'admin'; | |
| } | |
| else | |
| { // Frontoffice | |
| return 'front'; | |
| } | |
| } | |
| /** | |
| * Get an error message text about file permissions | |
| */ | |
| function get_file_permissions_message() | |
| { | |
| return sprintf( T_( '(Please check UNIX file permissions on the parent folder. %s)' ), get_manual_link( 'file-permissions' ) ); | |
| } | |
| /** | |
| * Flush the output buffer | |
| */ | |
| function evo_flush() | |
| { | |
| global $Timer, $disable_evo_flush; | |
| if( isset( $disable_evo_flush ) && $disable_evo_flush ) | |
| { // This function is disabled (for example, used for ajax/rest api requests) | |
| return; | |
| } | |
| // To fill the output buffer in order to flush data: | |
| // NOTE: Uncomment the below line if flush doesn't work as expected | |
| // echo str_repeat( ' ', 4096 ); | |
| // New line char is required as flag of that output portion is printed out: | |
| echo "\n"; | |
| $zlib_output_compression = ini_get( 'zlib.output_compression' ); | |
| if( empty( $zlib_output_compression ) || $zlib_output_compression == 'Off' ) | |
| { // This function helps to turn off output buffering | |
| // But do NOT use it when zlib.output_compression is ON, because it creates the die errors | |
| // fp/yura TODO: we need to optimize this: We want to flush to screen and continue caching. | |
| // This needs investigation and checking other similar places. | |
| global $PageCache; | |
| if( ! ( isset( $PageCache ) && ! empty( $PageCache->is_collecting ) ) ) | |
| { // Only when page cache is not running now because a notice error can appears in function PageCache->end_collect() | |
| @ob_end_flush(); | |
| } | |
| } | |
| flush(); | |
| if( isset( $Timer ) && $Timer->get_state( 'first_flush' ) == 'running' ) | |
| { // The first fulsh() was called, stop the timer | |
| $Timer->pause( 'first_flush' ); | |
| } | |
| } | |
| // ---------- APM : Application Performance Monitoring ----------- | |
| /** | |
| * Name the transaction for the APM. | |
| * This avoids that every request be called 'index.php' or 'evoadm.php' or 'cron_exec.php' | |
| * | |
| * @param mixed $request_transaction_name | |
| */ | |
| function apm_name_transaction( $request_transaction_name ) | |
| { | |
| if(extension_loaded('newrelic')) | |
| { // New Relic is installed on the server for monitoring. | |
| newrelic_name_transaction( $request_transaction_name ); | |
| } | |
| } | |
| /** | |
| * Log a custom metric | |
| * | |
| * @param mixed $name name of the custom metric | |
| * @param mixed $value assumed to be in milliseconds (ms) | |
| */ | |
| function apm_log_custom_metric( $name, $value ) | |
| { | |
| if(extension_loaded('newrelic')) | |
| { // New Relic is installed on the server for monitoring. | |
| newrelic_custom_metric( 'Custom/'.$name, $value ); | |
| } | |
| } | |
| /** | |
| * Log a custom param | |
| * | |
| * @param mixed $name name of the custom param | |
| * @param mixed $value of the custom param | |
| */ | |
| function apm_log_custom_param( $name, $value ) | |
| { | |
| if(extension_loaded('newrelic')) | |
| { // New Relic is installed on the server for monitoring. | |
| newrelic_add_custom_parameter( $name, $value ); | |
| } | |
| } | |
| /** | |
| * Echo JavaScript to edit values of column in the table list | |
| * | |
| * @param array Params | |
| */ | |
| function echo_editable_column_js( $params = array() ) | |
| { | |
| $params = array_merge( array( | |
| 'column_selector' => '', // jQuery selector of cell | |
| 'ajax_url' => '', // AJAX url to update a column value | |
| 'options' => array(), // Key = Value of option, Value = Title of option. Do not use Javascript code to populate this - use 'options_eval' param to do this. | |
| 'new_field_name' => '', // Name of _POST variable that will be send to ajax request with new value | |
| 'ID_value' => '', // jQuery to get value of ID | |
| 'ID_name' => '', // ID of field in DB | |
| 'tooltip' => T_('Click to edit'), | |
| 'colored_cells' => false, // Use TRUE when colors are used for background of cell | |
| 'print_init_tags' => true, // Use FALSE to don't print <script> tags if it is already used inside js | |
| 'field_type' => 'select', // Type of the editable field: 'select', 'text' | |
| 'field_class' => '', // Class of the editable field | |
| 'null_text' => '', // Null text of an input field, Use TS_() to translate it | |
| 'callback_code' => '', // Additional JS code after main callback code | |
| ), $params ); | |
| expose_var_to_js( $params['column_selector'], $params, 'evo_init_editable_column_config' ); | |
| } | |
| /** | |
| * Get a button class name depending on template | |
| * | |
| * @param string Type: 'button', 'button_text', 'button_group' | |
| * @param string TRUE - to get class value for jQuery selector | |
| * @return string Class name | |
| */ | |
| function button_class( $type = 'button', $jQuery_selector = false ) | |
| { | |
| // Default class names | |
| $classes = array( | |
| 'button' => 'roundbutton', // Simple button with icon | |
| 'button_red' => 'roundbutton_red', // Button with red background | |
| 'button_green' => 'roundbutton_green', // Button with green background | |
| 'text' => 'roundbutton_text', // Button with text | |
| 'text_primary' => 'roundbutton_text', // Button with text with special style color | |
| 'text_success' => 'roundbutton_text', // Button with text with special style color | |
| 'text_danger' => 'roundbutton_text', // Button with text with special style color | |
| 'group' => 'roundbutton_group', // Group of the buttons | |
| ); | |
| if( is_admin_page() ) | |
| { // Some admin skins may have special class names | |
| global $AdminUI; | |
| if( ! empty( $AdminUI ) ) | |
| { | |
| $template_classes = $AdminUI->get_template( 'button_classes' ); | |
| } | |
| } | |
| else | |
| { // Some front end skins may have special class names | |
| global $Skin; | |
| if( ! empty( $Skin ) ) | |
| { | |
| $template_classes = $Skin->get_template( 'button_classes' ); | |
| } | |
| } | |
| if( !empty( $template_classes ) ) | |
| { // Get class names from admin template | |
| $classes = array_merge( $classes, $template_classes ); | |
| } | |
| $class_name = isset( $classes[ $type ] ) ? $classes[ $type ] : ''; | |
| if( $jQuery_selector && ! empty( $class_name ) ) | |
| { // Convert class name to jQuery selector | |
| $class_name = '.'.str_replace( ' ', '.', $class_name ); | |
| } | |
| return $class_name; | |
| } | |
| /** | |
| * Initialize JavaScript to build and open window | |
| */ | |
| function echo_modalwindow_js() | |
| { | |
| global $AdminUI, $Collection, $Blog, $modal_window_js_initialized; | |
| if( ! empty( $modal_window_js_initialized ) ) | |
| { // Don't print out these functions twice | |
| return; | |
| } | |
| // TODO: asimo> Should not use AdminUI templates for the openModalWindow function. The style part should be handled by css. | |
| if( is_admin_page() && isset( $AdminUI ) && $AdminUI->get_template( 'modal_window_js_func' ) !== false ) | |
| { // Use the modal functions from back-office skin | |
| $skin_modal_window_js_func = $AdminUI->get_template( 'modal_window_js_func' ); | |
| } | |
| elseif( ! is_admin_page() && ! empty( $Blog ) ) | |
| { // Use the modal functions from front-office skin | |
| $blog_skin_ID = $Blog->get_skin_ID(); | |
| $SkinCache = & get_SkinCache(); | |
| $Skin = & $SkinCache->get_by_ID( $blog_skin_ID, false, false ); | |
| if( $Skin && $Skin->get_template( 'modal_window_js_func' ) !== false ) | |
| { | |
| $skin_modal_window_js_func = $Skin->get_template( 'modal_window_js_func' ); | |
| } | |
| } | |
| if( ! empty( $skin_modal_window_js_func ) && is_string( $skin_modal_window_js_func ) && function_exists( $skin_modal_window_js_func ) ) | |
| { // Call skin function only if it exists | |
| call_user_func( $skin_modal_window_js_func ); | |
| $modal_window_js_initialized = true; | |
| return; | |
| } | |
| $modal_window_js_initialized = true; | |
| } | |
| /** | |
| * Initialize JavaScript to build and open window for bootstrap skins | |
| */ | |
| function echo_modalwindow_js_bootstrap() | |
| { | |
| // Initialize variables for the file "bootstrap-evo_modal_window.js": | |
| echo '<script> | |
| var evo_js_lang_close = \''.TS_('Close').'\' | |
| var evo_js_lang_loading = \''.TS_('Loading...').'\'; | |
| var evo_js_lang_edit_image = \''.TS_('Insert or edit inline image').'\'; | |
| var evo_js_lang_select_image_insert = \''.TS_('Select image to insert').'\'; | |
| var evo_js_lang_alert_before_insert_item = \''.TS_('Save post to start uploading files').'\'; | |
| var evo_js_lang_alert_before_insert_comment = \''.TS_('Save comment to start uploading files').'\'; | |
| var evo_js_lang_alert_before_insert_emailcampaign = \''.TS_('Save email campaign to start uploading files').'\'; | |
| var evo_js_lang_alert_before_insert_message = \''.TS_('Save message to start uploading files').'\'; | |
| </script>'; | |
| } | |
| /** | |
| * Initialize JavaScript to build and open WHOIS window | |
| */ | |
| function echo_whois_js_bootstrap() | |
| { | |
| echo '<script> | |
| var evo_js_lang_close = \''.TS_('Close').'\'; | |
| var evo_js_lang_loading = \''.TS_('Loading...').'\'; | |
| var evo_js_lang_whois_title = \''.TS_('Querying WHOIS server...').'\'; | |
| </script>'; | |
| } | |
| /** | |
| * Handle fatal error in order to display info message when debug is OFF | |
| */ | |
| function evo_error_handler() | |
| { | |
| global $evo_last_handled_error; | |
| // Get last error | |
| $error = error_get_last(); | |
| if( ! empty( $error ) && $error['type'] === E_ERROR ) | |
| { // Save only last fatal error | |
| $evo_last_handled_error = $error; | |
| } | |
| // fp> WTF?!? and what about warnings? | |
| // fp> And where do we die()? why is there not a debug_die() here? | |
| // There should be ONE MILLION COMMENTS in this function to explain what we do! | |
| } | |
| /** | |
| * Get icon to collapse/expand fieldset | |
| * | |
| * @param string ID of fieldset | |
| * @param array Params | |
| * @return string Icon with hidden input field | |
| */ | |
| function get_fieldset_folding_icon( $id, $params = array() ) | |
| { | |
| if( ! is_logged_in() ) | |
| { // Only loggedin users can fold fieldset | |
| return; | |
| } | |
| $params = array_merge( array( | |
| 'before' => '', | |
| 'after' => ' ', | |
| 'deny_fold' => false, // TRUE to don't allow fold the block and keep it opened always on page loading | |
| 'default_fold' => NULL, // Set default "fold" value for current icon | |
| 'fold_value' => NULL, | |
| ), $params ); | |
| if( $params['deny_fold'] ) | |
| { // Deny folding for this case | |
| $value = 0; | |
| } | |
| elseif( ! is_null( $params['fold_value'] ) ) | |
| { // Fold value is specified, use this: | |
| $value = intval( $params['fold_value'] ); | |
| } | |
| else | |
| { // Get the fold value from user settings | |
| global $UserSettings, $Collection, $Blog, $ctrl; | |
| if( empty( $Blog ) || ( isset( $ctrl ) && in_array( $ctrl, array( 'plugins', 'user' ) ) ) ) | |
| { // Get user setting value | |
| $value = $UserSettings->get( 'fold_'.$id ); | |
| } | |
| else | |
| { // Get user-collection setting | |
| $value = $UserSettings->get_collection_setting( 'fold_'.$id, $Blog->ID ); | |
| } | |
| if( $value === NULL && $params['default_fold'] !== NULL ) | |
| { // Use custom default value for this icon: | |
| $value = $params['default_fold']; | |
| } | |
| $value = intval( $value ); | |
| } | |
| // Icon | |
| if( $value ) | |
| { | |
| $icon_current = 'filters_show'; | |
| $icon_reverse = 'filters_hide'; | |
| $title_reverse = T_('Collapse'); | |
| } | |
| else | |
| { | |
| $icon_current = 'filters_hide'; | |
| $icon_reverse = 'filters_show'; | |
| $title_reverse = T_('Expand'); | |
| } | |
| $icon = get_icon( $icon_current, 'imgtag', array( | |
| 'id' => 'icon_folding_'.$id, | |
| 'data-xy' => get_icon( $icon_reverse, 'xy' ), | |
| 'data-title' => format_to_output( $title_reverse, 'htmlattr' ), | |
| ) ); | |
| // Hidden input to store current value of the folding status | |
| $hidden_input = '<input type="hidden" name="folding_values['.$id.']" id="folding_value_'.$id.'" value="'.$value.'" />'; | |
| return $hidden_input.$params['before'].$icon.$params['after']; | |
| } | |
| /** | |
| * Output JavaScript code to collapse/expand fieldset | |
| */ | |
| function echo_fieldset_folding_js() | |
| { | |
| if( ! is_logged_in() ) | |
| { // Only loggedin users can fold fieldset | |
| return; | |
| } | |
| expose_var_to_js( 'evo_fieldset_folding_config', true ); | |
| } | |
| /** | |
| * Save the values of fieldset folding into DB | |
| * | |
| * @param integer Blog ID is used to save setting per blog, NULL- to don't save per blog | |
| */ | |
| function save_fieldset_folding_values( $blog_ID = NULL ) | |
| { | |
| if( ! is_logged_in() ) | |
| { // Only loggedin users can fold fieldset | |
| return; | |
| } | |
| $folding_values = param( 'folding_values', 'array:integer' ); | |
| if( empty( $folding_values ) ) | |
| { // No folding values go from request, Exit here | |
| return; | |
| } | |
| global $UserSettings; | |
| foreach( $folding_values as $key => $value ) | |
| { | |
| $setting_name = 'fold_'.$key; | |
| if( $blog_ID !== NULL ) | |
| { // Save setting per blog | |
| $setting_name .= '_'.$blog_ID; | |
| } | |
| $UserSettings->set( $setting_name, $value ); | |
| } | |
| // Update the folding setting for current user | |
| $UserSettings->dbupdate(); | |
| } | |
| /** | |
| * Save the values of active tab pane into DB | |
| * | |
| * @param integer Blog ID is used to save setting per blog, NULL- to don't save per blog | |
| */ | |
| function save_active_tab_pane_value( $blog_ID = NULL ) | |
| { | |
| if( ! is_logged_in() ) | |
| { // Only loggedin users can fold fieldset | |
| return; | |
| } | |
| $tab_pane_value = param( 'tab_pane_active', 'array:string' ); | |
| if( empty( $tab_pane_value ) ) | |
| { // No tab pane value go from request, Exit here | |
| return; | |
| } | |
| global $UserSettings; | |
| if( is_array( $tab_pane_value ) && ! empty( $tab_pane_value ) ) | |
| { // Get first key: | |
| $key = array_keys( $tab_pane_value ); | |
| $key = trim( $key[0] ); | |
| } | |
| else | |
| { | |
| $key = ''; | |
| } | |
| $value = ( isset( $tab_pane_value[ $key ] ) ) ? $tab_pane_value[ $key ] : ''; | |
| $value = trim( $value ); | |
| if( ! empty( $key ) && ! empty( $value ) ) | |
| { | |
| $setting_name = 'active_'.$key; | |
| if( $blog_ID !== NULL ) | |
| { // Save setting per blog | |
| $setting_name .= '_'.$blog_ID; | |
| } | |
| $UserSettings->set( $setting_name, $value ); | |
| // Update the folding setting for current user | |
| $UserSettings->dbupdate(); | |
| } | |
| } | |
| /** | |
| * Get html code of bootstrap dropdown element | |
| * | |
| * @param array Params | |
| */ | |
| function get_status_dropdown_button( $params = array() ) | |
| { | |
| $params = array_merge( array( | |
| 'name' => '', | |
| 'value' => '', | |
| 'title_format' => '', | |
| 'options' => NULL, | |
| 'exclude_statuses' => array( 'trash' ), | |
| ), $params ); | |
| if( $params['options'] === NULL ) | |
| { // Get status options by title format: | |
| $status_options = get_visibility_statuses( $params['title_format'], $params['exclude_statuses'] ); | |
| } | |
| else | |
| { // Use status options from params: | |
| $status_options = $params['options']; | |
| } | |
| $status_icon_options = get_visibility_statuses( 'icons', $params['exclude_statuses'] ); | |
| $r = '<div class="btn-group dropdown autoselected" data-toggle="tooltip" data-placement="top" data-container="body" title="'.get_status_tooltip_title( $params['value'] ).'">'; | |
| $r .= '<button type="button" class="btn btn-status-'.$params['value'].' dropdown-toggle" data-toggle="dropdown" aria-expanded="false">' | |
| .'<span>'.$status_options[ $params['value'] ].'</span>' | |
| .' <span class="caret"></span></button>'; | |
| $r .= '<ul class="dropdown-menu" role="menu" aria-labelledby="'.$params['name'].'">'; | |
| foreach( $status_options as $status_key => $status_title ) | |
| { | |
| $r .= '<li rel="'.$status_key.'" role="presentation"><a href="#" role="menuitem" tabindex="-1">'.$status_icon_options[ $status_key ].' <span>'.$status_title.'</span></a></li>'; | |
| } | |
| $r .= '</ul>'; | |
| $r .= '</div>'; | |
| return $r; | |
| } | |
| /** | |
| * Output JavaScript code to work with dropdown bootstrap element | |
| */ | |
| function echo_form_dropdown_js() | |
| { | |
| // Build a string to initialize javascript array with button titles | |
| $tooltip_titles = get_visibility_statuses( 'tooltip-titles' ); | |
| $tooltip_titles_js_array = array(); | |
| foreach( $tooltip_titles as $status => $tooltip_title ) | |
| { | |
| $tooltip_titles_js_array[] = $status.': \''.TS_( $tooltip_title ).'\''; | |
| } | |
| $tooltip_titles_js_array = implode( ', ', $tooltip_titles_js_array ); | |
| ?> | |
| <script> | |
| jQuery( '.btn-group.dropdown.autoselected li a' ).on( 'click', function() | |
| { | |
| var item_status_tooltips = {<?php echo $tooltip_titles_js_array ?>}; | |
| var item = jQuery( this ).parent(); | |
| var status = item.attr( 'rel' ); | |
| var btn_group = item.parent().parent(); | |
| var button = jQuery( item.parent().parent().find( 'button:first' ) ); | |
| var field_name = jQuery( this ).parent().parent().attr( 'aria-labelledby' ); | |
| // Change status class name to new changed for all buttons: | |
| button.attr( 'class', button.attr( 'class' ).replace( /btn-status-[^\s]+/, 'btn-status-' + status ) ); | |
| // Update selector button to status title: | |
| button.find( 'span:first' ).html( item.find( 'span:last' ).html() ); // update selector button to status title | |
| // Update hidden field to new status value: | |
| jQuery( 'input[type=hidden][name=' + field_name + ']' ).val( status ); | |
| // Hide dropdown menu: | |
| item.parent().parent().removeClass( 'open' ); | |
| // Update tooltip | |
| btn_group.tooltip( 'hide' ).attr( 'data-original-title', item_status_tooltips[status] ).tooltip( 'show' ); | |
| return false; | |
| } ); | |
| </script> | |
| <?php | |
| } | |
| /** | |
| * Get baseurl depending on current called script | |
| * | |
| * @return string URL | |
| */ | |
| function get_script_baseurl() | |
| { | |
| if( isset( $_SERVER['SERVER_NAME'] ) ) | |
| { // Set baseurl from current server name | |
| $temp_baseurl = 'http://'.$_SERVER['SERVER_NAME']; | |
| if( isset( $_SERVER['SERVER_PORT'] ) ) | |
| { | |
| if( $_SERVER['SERVER_PORT'] == '443' ) | |
| { // Rewrite that as https: | |
| $temp_baseurl = 'https://'.$_SERVER['SERVER_NAME']; | |
| } | |
| elseif( $_SERVER['SERVER_PORT'] == '8890' ) | |
| { // Used for testing | |
| $temp_baseurl = 'https://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT']; | |
| } | |
| elseif( $_SERVER['SERVER_PORT'] != '80' ) | |
| { // Get also a port number | |
| $temp_baseurl .= ':'.$_SERVER['SERVER_PORT']; | |
| } | |
| } | |
| if( isset( $_SERVER['SCRIPT_NAME'] ) ) | |
| { // Get also the subfolders, when script is called e.g. from http://localhost/blogs/b2evolution/ | |
| $temp_baseurl .= preg_replace( '~(.*/)[^/]*$~', '$1', $_SERVER['SCRIPT_NAME'] ); | |
| } | |
| } | |
| else | |
| { // Use baseurl from config | |
| global $baseurl; | |
| $temp_baseurl = $baseurl; | |
| } | |
| return $temp_baseurl; | |
| } | |
| /** | |
| * Get badge to inform the settings are edited only by collection/user admins | |
| * | |
| * @param string Type: 'coll', 'user' | |
| * @param string Manual URL, '#' - default, false - don't set URL | |
| * @param string Text | |
| * @param string Title | |
| * @param string Value | |
| * @return string | |
| */ | |
| function get_admin_badge( $type = 'coll', $manual_url = '#', $text = '#', $title = '#', $value = NULL ) | |
| { | |
| switch( $type ) | |
| { | |
| case 'coll': | |
| if( $text == '#' ) | |
| { // Use default text: | |
| $text = T_('Coll. Admin'); | |
| } | |
| if( $title == '#' ) | |
| { // Use default title: | |
| $title = T_('This can only be edited by users with the Collection Admin permission.'); | |
| } | |
| if( $manual_url == '#' ) | |
| { // Use default manual url: | |
| $manual_url = 'collection-admin'; | |
| } | |
| break; | |
| case 'user': | |
| if( $text == '#' ) | |
| { // Use default text: | |
| $text = T_('User Admin'); | |
| } | |
| if( $title == '#' ) | |
| { // Use default title: | |
| $title = T_('This can only be edited by users with the User Admin permission.'); | |
| } | |
| if( $manual_url == '#' ) | |
| { // Use default manual url: | |
| $manual_url = 'user-admin'; | |
| } | |
| break; | |
| default: | |
| // Unknown badge type: | |
| return ''; | |
| } | |
| if( empty( $manual_url ) ) | |
| { // Don't use a link: | |
| $r = ' <b'; | |
| } | |
| else | |
| { // Use link: | |
| $r = ' <a href="'.get_manual_url( $manual_url ).'" target="_blank"'; | |
| } | |
| $r .= ' class="badge badge-warning"'; | |
| if( ! empty( $title ) && $title != '#' ) | |
| { // Use title for tooltip: | |
| $r .= ' data-toggle="tooltip" data-placement="top" title="'.format_to_output( $title, 'htmlattr' ).'"'; | |
| } | |
| $r .= '>'; | |
| $r .= $text; | |
| if( empty( $manual_url ) ) | |
| { // End of text formatted badge: | |
| $r .= '</b>'; | |
| } | |
| else | |
| { // End of the link: | |
| $r .= '</a>'; | |
| } | |
| return $r; | |
| } | |
| /** | |
| * Compares two "PHP-standardized" version number strings | |
| * | |
| * @param string First version number, Use 'current' for global $app_version | |
| * @param string Second version number | |
| * @param string If the third optional operator argument is specified, test for a particular relationship. | |
| * The possible operators are: <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne respectively. | |
| * This parameter is case-sensitive, values should be lowercase. | |
| * @return integer|boolean -1 if the first version is lower than the second, 0 if they are equal, and 1 if the second is lower. | |
| * When using the optional operator argument, the function will return TRUE if the relationship is the one specified by the operator, FALSE otherwise. | |
| */ | |
| function evo_version_compare( $version1, $version2, $operator = NULL ) | |
| { | |
| if( $version1 === 'current' ) | |
| { // Use current version of application: | |
| global $app_version; | |
| $version1 = $app_version; | |
| } | |
| preg_match( '#^([\d\.]+)(-.+)?$#', $version1, $m_ver1 ); | |
| preg_match( '#^([\d\.]+)(-.+)?$#', $version2, $m_ver2 ); | |
| if( isset( $m_ver1[1], $m_ver2[1] ) && $m_ver1[1] == $m_ver2[1] ) | |
| { // If versions number is same: | |
| $version1_suffix = ( isset( $m_ver1[2] ) ? $m_ver1[2] : '' ); | |
| $version2_suffix = ( isset( $m_ver2[2] ) ? $m_ver2[2] : '' ); | |
| if( $version1_suffix == '-PRO' ) | |
| { // Remove "PRO" suffix to compare such versions as upper than "stable", "alpha", "beta" and etc.: | |
| $version1 = $m_ver1[1]; | |
| if( $version2_suffix === '' ) | |
| { // Add suffix "stable" in order to make version(without suffix) lower than "PRO": | |
| $version2 .= '-stable'; | |
| } | |
| } | |
| elseif( $version2_suffix == '-stable' ) | |
| { // Remove "stable" suffix to compare such versions as upper than "alpha", "beta" and etc. except of "PRO": | |
| $version2 = $m_ver2[1]; | |
| } | |
| if( $version2_suffix == '-PRO' ) | |
| { // Remove "PRO" suffix to compare such versions as upper than "stable", "alpha", "beta" and etc.: | |
| $version2 = $m_ver2[1]; | |
| if( $version1_suffix === '' ) | |
| { // Add suffix "stable" in order to make version(without suffix) lower than "PRO": | |
| $version1 .= '-stable'; | |
| } | |
| } | |
| elseif( $version1_suffix == '-stable' ) | |
| { // Remove "stable" suffix to compare such versions as upper than "alpha", "beta" and etc. except of "PRO": | |
| $version1 = $m_ver1[1]; | |
| } | |
| } | |
| if( is_null( $operator ) ) | |
| { // To return integer: | |
| return version_compare( $version1, $version2 ); | |
| } | |
| else | |
| { // To return boolean: | |
| return version_compare( $version1, $version2, $operator ); | |
| } | |
| } | |
| /** | |
| * Get text for install page depending on param $display == 'cli' | |
| * | |
| * @param string Original text | |
| * @param string Format (Used for CLI mode) | |
| * @return string Prepared text | |
| */ | |
| function get_install_format_text_and_log( $text, $format = 'string' ) | |
| { | |
| global $display, $logs_path, $log_file_handle, $avoid_log_file; | |
| if( empty( $display ) || $display != 'cli' ) | |
| { // Don't touch text for non CLI modes: | |
| if( ! $avoid_log_file ) | |
| { // Include in log file: | |
| prepare_install_log_message( $text ); | |
| } | |
| return $text; | |
| } | |
| // Don't remove these HTML tags on CLI mode: | |
| $allowable_html_tags = '<evo:error><evo:warning><evo:success><evo:note><evo:login><evo:password>'; | |
| // Remove all new lines because we build them from requested format: | |
| $text = str_replace( array( "\n", "\r" ), '', $text ); | |
| // Keep all URLs and display them | |
| $text = preg_replace( '/<a[^>]+href="([^"]+)"[^>]*>(.+)<\/a>/i', '$2(URL: $1)', $text ); | |
| // Remove HTML tags from text: | |
| $text = strip_tags( $text, $allowable_html_tags ); | |
| switch( $format ) | |
| { | |
| case 'h2': | |
| // Header 2 | |
| $text = "\n\n----- ".$text." -----\n\n"; | |
| break; | |
| case 'br': | |
| // Paragraph: | |
| $text = $text."\n"; | |
| break; | |
| case 'p': | |
| // Paragraph: | |
| $text = "\n".$text."\n\n"; | |
| break; | |
| case 'p-start': | |
| // Start paragraph: | |
| $text = "\n".$text; | |
| break; | |
| case 'p-start-br': | |
| // Start paragraph: | |
| $text = "\n".$text."\n"; | |
| break; | |
| case 'p-end': | |
| // End paragraph: | |
| $text = $text."\n\n"; | |
| break; | |
| case 'li': | |
| // List item: | |
| $text = "\n- ".$text."\n"; | |
| break; | |
| case 'code': | |
| // Code: | |
| $text = "\n================\n".$text."\n================\n"; | |
| break; | |
| } | |
| // Replace all html entities like " ", "»", "«" to readable chars: | |
| $text = html_entity_decode( $text ); | |
| if( ! $avoid_log_file ) | |
| { // Include in log file: | |
| prepare_install_log_message( $text ); | |
| } | |
| return $text; | |
| } | |
| /** | |
| * Start to log into file on disk | |
| */ | |
| function start_install_log( $log_file_name ) | |
| { // TODO: Factorize with start_log(): | |
| global $rsc_url, $app_version_long, $log_file_handle, $logs_path, $servertimenow; | |
| // Get file path for log: | |
| $log_file_path = $logs_path.date( 'Y-m-d-H-i-s', $servertimenow ).'-'.$log_file_name.'.html'; | |
| // Check log path is writeable or not: | |
| if ( ! is_writable( $logs_path ) ) { | |
| return false; | |
| } | |
| // Try to create log file: | |
| if( ! ( $log_file_handle = fopen( $log_file_path, 'w' ) ) ) | |
| { | |
| return false; | |
| } | |
| // Write header of the log file: | |
| install_log_to_file( '<!DOCTYPE html>'."\r\n" | |
| .'<html lang="en-US">'."\r\n" | |
| .'<head>'."\r\n" | |
| .'<link href="'.$rsc_url.'css/bootstrap/bootstrap.css?v='.$app_version_long.'" type="text/css" rel="stylesheet" />'."\r\n" | |
| .'</head>'."\r\n" | |
| .'<body>'."\r\n" | |
| .'<div style="padding:5px">' ); | |
| } | |
| /** | |
| * End of log into file on disk | |
| */ | |
| function end_install_log() | |
| { // TODO: Factorize with end_log(): | |
| global $log_file_handle; | |
| // Write footer of the log file: | |
| install_log_to_file( '</div>'."\r\n" | |
| .'</body>'."\r\n" | |
| .'</html>' ); | |
| if( isset( $log_file_handle ) && $log_file_handle ) | |
| { // Close the log file: | |
| fclose( $log_file_handle ); | |
| } | |
| } | |
| /** | |
| * Log a message on screen and into file on disk | |
| * | |
| * @param string Message | |
| * @param string Type: 'success', 'error', 'warning' | |
| * @param string HTML tag for type/styled log: 'p', 'span', 'b', etc. | |
| * @param boolean TRUE to display label | |
| */ | |
| function prepare_install_log_message( $message, $type = NULL, $type_html_tag = 'p', $display_label = true ) | |
| { // TODO: Factorize with log(): | |
| global $log_file_handle; | |
| if( ! isset( $log_file_handle ) || ! $log_file_handle ) | |
| { | |
| return false; | |
| } | |
| $message = get_install_log( $message, $type, $type_html_tag, $display_label ); | |
| if( $message === false ) | |
| { // Skip when message should not be displayed: | |
| return; | |
| } | |
| // Try to store a message into the log file on the disk: | |
| install_log_to_file( $message ); | |
| } | |
| /** | |
| * Get a log message | |
| * | |
| * @param string Message | |
| * @param string Type: 'success', 'error', 'warning', 'info' | |
| * @param string HTML tag for type/styled log: 'p', 'span', 'b', etc. | |
| * @param boolean TRUE to display label | |
| * @return string|FALSE Formatted log message, FALSE - when message should not be displayed | |
| */ | |
| function get_install_log( $message, $type = NULL, $type_html_tag = 'p', $display_label = true ) | |
| { // TODO: Factorize with get_log(): | |
| if( $message === '' ) | |
| { // Don't log empty strings: | |
| return false; | |
| } | |
| switch( $type ) | |
| { | |
| case 'success': | |
| $before = '<'.$type_html_tag.' class="text-success"> '; | |
| $after = '</'.$type_html_tag.'>'; | |
| break; | |
| case 'error': | |
| $before = '<'.$type_html_tag.' class="text-danger">'.( $display_label ? '<span class="label label-danger">ERROR</span>' : '' ).' '; | |
| $after = '</'.$type_html_tag.'>'; | |
| break; | |
| case 'warning': | |
| $before = '<'.$type_html_tag.' class="text-warning">'.( $display_label ? '<span class="label label-warning">WARNING</span>' : '' ).' '; | |
| $after = '</'.$type_html_tag.'>'; | |
| break; | |
| case 'info': | |
| $before = '<'.$type_html_tag.' class="text-info">'.( $display_label ? '<span class="label label-info">INFO</span>' : '' ).' '; | |
| $after = '</'.$type_html_tag.'>'; | |
| break; | |
| default: | |
| $before = ''; | |
| $after = ''; | |
| break; | |
| } | |
| return $before.$message.$after; | |
| } | |
| /** | |
| * Log a message into file on disk | |
| * | |
| * @param string Message | |
| */ | |
| function install_log_to_file( $message ) | |
| { // TODO: Factorize with log_to_file(): | |
| global $log_file_handle; | |
| if( ! isset( $log_file_handle ) || ! $log_file_handle ) | |
| { | |
| return false; | |
| } | |
| // Put a message into the log file on the disk: | |
| fwrite( $log_file_handle, $message."\r\n" ); | |
| } | |
| /** | |
| * Check if password should be transmitted in hashed format during Login | |
| * | |
| * @return boolean TRUE - hashed password will be transmitted, FALSE - raw password will be transmitted | |
| */ | |
| function can_use_hashed_password() | |
| { | |
| global $transmit_hashed_password; | |
| if( isset( $transmit_hashed_password ) ) | |
| { // Get value from already defined var: | |
| return $transmit_hashed_password; | |
| } | |
| global $Settings, $Plugins; | |
| // Allow to transmit hashed password only when: | |
| // - it is enabled by general setting "Password hashing during Login" | |
| // - no plugins that automatically disable this option during Login | |
| $transmit_hashed_password = (bool)$Settings->get( 'js_passwd_hashing' ) && !(bool)$Plugins->trigger_event_first_true( 'LoginAttemptNeedsRawPassword' ); | |
| return $transmit_hashed_password; | |
| } | |
| /** | |
| * Convert inline file tags like [image|file:123:link title:.css_class_name] or [inline:123:.css_class_name] into HTML tags | |
| * | |
| * @param string Source content | |
| * @param object Source object: Item, Comment, EmailCampaign, Message | |
| * @param array Params | |
| * @return string Content | |
| */ | |
| function render_inline_files( $content, $Object, $params = array() ) | |
| { | |
| $params = array_merge( array( | |
| 'check_code_block' => false, | |
| 'clear_paragraph' => true, | |
| 'render_tag_image' => true, | |
| 'render_tag_file' => true, | |
| 'render_tag_inline' => true, | |
| 'render_tag_video' => true, | |
| 'render_tag_audio' => true, | |
| 'render_tag_thumbnail' => true, | |
| 'render_tag_folder' => true, | |
| ), $params ); | |
| $render_tags = array(); | |
| if( $params['render_tag_image'] ) | |
| { // Render short tag [image:] | |
| $render_tags[] = 'image'; | |
| } | |
| if( $params['render_tag_file'] ) | |
| { // Render short tag [file:] | |
| $render_tags[] = 'file'; | |
| } | |
| if( $params['render_tag_inline'] ) | |
| { // Render short tag [inline:] | |
| $render_tags[] = 'inline'; | |
| } | |
| if( $params['render_tag_video'] ) | |
| { // Render short tag [video:] | |
| $render_tags[] = 'video'; | |
| } | |
| if( $params['render_tag_audio'] ) | |
| { // Render short tag [audio:] | |
| $render_tags[] = 'audio'; | |
| } | |
| if( $params['render_tag_thumbnail'] ) | |
| { // Render short tag [thumbnail:] | |
| $render_tags[] = 'thumbnail'; | |
| } | |
| if( $params['render_tag_folder'] ) | |
| { // Render short tag [folder:] | |
| $render_tags[] = 'folder'; | |
| } | |
| if( empty( $render_tags ) ) | |
| { // No tags for rendering: | |
| return $content; | |
| } | |
| if( $params['check_code_block'] && ( ( stristr( $content, '<code' ) !== false ) || ( stristr( $content, '<pre' ) !== false ) ) ) | |
| { // Call render_inline_files() on everything outside code/pre: | |
| $params['check_code_block'] = false; | |
| $content = callback_on_non_matching_blocks( $content, | |
| '~<(code|pre)[^>]*>.*?</\1>~is', | |
| 'render_inline_files', array( $Object, $params ) ); | |
| return $content; | |
| } | |
| // No code/pre blocks, replace on the whole thing | |
| if( $params['clear_paragraph'] ) | |
| { // Remove block level short tags inside <p> blocks and move them before the paragraph: | |
| $content = move_short_tags( $content ); | |
| } | |
| // Find all matches with inline tags | |
| preg_match_all( '/\[('.implode( '|', $render_tags ).'):(\d+)(:?)([^\]]*)\]/i', $content, $inlines ); | |
| if( !empty( $inlines[0] ) ) | |
| { // There are inline tags in the content... | |
| $rendered_tags = render_inline_tags( $Object, $inlines[0], $params ); | |
| if( $rendered_tags ) | |
| { // Do replacing if the object really contains inline attached tags: | |
| foreach( $rendered_tags as $current_link_tag => $rendered_link_tag ) | |
| { | |
| $content = str_replace( $current_link_tag, $rendered_link_tag, $content ); | |
| } | |
| } | |
| } | |
| return $content; | |
| } | |
| /** | |
| * Convert inline tags like [image:|file:|inline:|video:|audio:|thumbnail:|folder:] into HTML tags | |
| * | |
| * @param object Source object: Item, Comment, EmailCampaign, Message | |
| * @param array Inline tags | |
| * @param array Params | |
| * @return array Associative array of rendered HTML tags with inline tags as key | |
| */ | |
| function render_inline_tags( $Object, $tags, $params = array() ) | |
| { | |
| global $Plugins; | |
| $inlines = array(); | |
| $object_class = get_class( $Object ); | |
| $params = array_merge( array( | |
| 'before' => '<div>', | |
| 'before_image' => '<div'.( $object_class == 'EmailCampaign' ? emailskin_style( '.image_block' ) : ' class="image_block"' ).'>', | |
| 'before_image_legend' => '<div'.( $object_class == 'EmailCampaign' ? emailskin_style( '.image_legend' ) : ' class="image_legend"' ).'>', | |
| 'after_image_legend' => '</div>', | |
| 'after_image' => '</div>', | |
| 'after' => '</div>', | |
| 'image_size' => 'fit-400x320', | |
| 'image_link_to' => 'original', // Can be 'orginal' (image) or 'single' (this post) | |
| 'limit' => 1000, // Max # of images displayed | |
| ), $params ); | |
| if( !isset( $LinkList ) ) | |
| { // Get list of attached Links only first time: | |
| if( $Object->ID == 0 ) | |
| { // Get temporary object ID on preview new creating object: | |
| $temp_link_owner_ID = param( 'temp_link_owner_ID', 'integer', NULL ); | |
| } | |
| else | |
| { // Don't use temporary object for existing object: | |
| $temp_link_owner_ID = NULL; | |
| } | |
| switch( $object_class ) | |
| { | |
| case 'Item': | |
| $LinkOwner = new LinkItem( $Object, $temp_link_owner_ID ); | |
| $prepare_plugin_event_name = 'PrepareForRenderItemAttachment'; | |
| $render_plugin_event_name = 'RenderItemAttachment'; | |
| break; | |
| case 'Comment': | |
| $LinkOwner = new LinkComment( $Object, empty( $Object->temp_link_owner_ID ) ? $temp_link_owner_ID : $Object->temp_link_owner_ID ); | |
| $prepare_plugin_event_name = 'PrepareForRenderCommentAttachment'; | |
| $render_plugin_event_name = 'RenderCommentAttachment'; | |
| break; | |
| case 'EmailCampaign': | |
| $LinkOwner = new LinkEmailCampaign( $Object ); | |
| $prepare_plugin_event_name = 'PrepareForRenderEmailAttachment'; | |
| $render_plugin_event_name = 'RenderEmailAttachment'; | |
| break; | |
| case 'Message': | |
| $LinkOwner = new LinkMessage( $Object, $temp_link_owner_ID ); | |
| $prepare_plugin_event_name = 'PrepareForRenderMessageAttachment'; | |
| $render_plugin_event_name = 'RenderMessageAttachment'; | |
| break; | |
| default: | |
| // Wrong source object type: | |
| return false; | |
| } | |
| $LinkList = $LinkOwner->get_attachment_LinkList( $params['limit'] ); | |
| } | |
| if( empty( $LinkList ) ) | |
| { // This Object has no attached files for 'inline' position, Exit here: | |
| return false; | |
| } | |
| // Render inline tags by active plugins: | |
| $plugins_inlines = $Plugins->trigger_collect( 'RenderInlineTags', array_merge( $params, array( | |
| 'Object' => $Object, | |
| 'inline_tags' => $tags, | |
| ) ) ); | |
| foreach( $plugins_inlines as $plugin_ID => $plugin_inlines ) | |
| { | |
| $inlines = array_merge( $inlines, $plugin_inlines ); | |
| } | |
| foreach( $tags as $current_inline ) | |
| { | |
| if( isset( $inlines[$current_inline] ) ) | |
| { // Skip inline tag if it has been already rendered before, e-g by some plugin in the event "RenderInlineTags" above: | |
| continue; | |
| } | |
| if( ! preg_match( '/\[(image|file|inline|video|audio|thumbnail|folder):(\d+)(:?)([^\]]*)\]/i', $current_inline, $inline ) ) | |
| { // Don't render a not supported inline tag: | |
| $inlines[$current_inline] = $current_inline; | |
| continue; | |
| } | |
| $inline_type = $inline[1]; // image|file|inline|video|audio|thumbnail|folder | |
| $current_link_ID = (int) $inline[2]; | |
| if( empty( $current_link_ID ) ) | |
| { // Invalid link ID, Go to next match | |
| $inlines[$current_inline] = $current_inline; | |
| continue; | |
| } | |
| if( ! ( $Link = & $LinkList->get_by_field( 'link_ID', $current_link_ID ) ) ) | |
| { // Link ID is not part of the linked files for position "inline" | |
| $inlines[$current_inline] = $current_inline; | |
| continue; | |
| } | |
| if( ! ( $File = & $Link->get_File() ) ) | |
| { // No File object: | |
| global $Debuglog; | |
| $Debuglog->add( sprintf( 'Link ID#%d of '.$object_class.' #%d does not have a file object!', $Link->ID, $Object->ID ), array( 'error', 'files' ) ); | |
| $inlines[$current_inline] = $current_inline; | |
| continue; | |
| } | |
| if( ! $File->exists() ) | |
| { // File doesn't exist: | |
| global $Debuglog; | |
| $Debuglog->add( sprintf( 'File linked to '.$object_class.' #%d does not exist (%s)!', $Object->ID, $File->get_full_path() ), array( 'error', 'files' ) ); | |
| $inlines[$current_inline] = $current_inline; | |
| continue; | |
| } | |
| $params['File'] = $File; | |
| $params['Link'] = $Link; | |
| $params[ $object_class ] = $Object; | |
| $current_file_params = array(); | |
| switch( $inline_type ) | |
| { | |
| case 'image': | |
| case 'inline': // valid file type: image | |
| if( $File->is_image() ) | |
| { | |
| $current_image_params = $params; | |
| $image_href = false; | |
| $image_rel = NULL; | |
| $image_additional_class = false; | |
| if( ! empty( $inline[3] ) ) // check if second colon is present | |
| { | |
| // Get the inline params: caption and class | |
| $inline_params = explode( ':', $inline[4] ); | |
| $opt_index = 0; | |
| // Caption: | |
| if( $inline_type != 'inline' && ! empty( $inline_params[0] ) ) | |
| { // Caption is set, so overwrite the image link title | |
| if( $inline_params[0] == '-' ) | |
| { // Caption display is disabled | |
| $current_image_params['image_link_title'] = ''; | |
| $current_image_params['hide_image_link_title'] = true; | |
| } | |
| else | |
| { // New image caption was set | |
| $current_image_params['image_link_title'] = strip_tags( $inline_params[0] ); | |
| } | |
| $current_image_params['image_desc'] = $current_image_params['image_link_title']; | |
| $current_file_params['title'] = $inline_params[0]; | |
| } | |
| if( $inline_type == 'image' ) | |
| { // Caption has always a reserved placefor image short tag: | |
| $opt_index++; | |
| } | |
| // RegExp to detect HRef option: | |
| $href_regexp = '#^(https?|\(\((.*?)\)\))$#i'; | |
| // Alt text: | |
| $current_image_params['image_alt'] = ''; | |
| if( isset( $inline_params[ $opt_index ] ) && | |
| substr( $inline_params[ $opt_index ], 0, 1 ) != '.' && | |
| ! preg_match( $href_regexp, $inline_params[ $opt_index ] ) && | |
| ( $inline_type == 'image' || ! in_array( $inline_params[ $opt_index ], array( 'small', 'medium', 'large', 'original' ) ) ) ) | |
| { // Override the image File's alt text with provided in current inline tag: | |
| if( $inline_params[ $opt_index ] == '-' ) | |
| { // Alt text display is disabled: | |
| $current_image_params['image_alt'] = '-'; | |
| } | |
| else | |
| { // New image alt text was set: | |
| $current_image_params['image_alt'] = strip_tags( $inline_params[ $opt_index ] ); | |
| } | |
| $opt_index++; | |
| } | |
| // HRef: | |
| if( $inline_type != 'inline' && | |
| ! empty( $inline_params[ $opt_index ] ) && | |
| preg_match( $href_regexp, $inline_params[ $opt_index ], $href_match ) ) | |
| { | |
| if( stripos( $href_match[0], 'http' ) === 0 ) | |
| { // Absolute URL: | |
| $image_href = $href_match[0].':'.$inline_params[ $opt_index + 1 ]; | |
| $image_rel = ''; // reset default attribute "rel" to don't display colorbox on click | |
| $opt_index++; | |
| } | |
| else | |
| { // Item slug: | |
| $ItemCache = & get_ItemCache(); | |
| if( $href_match[2] === '' ) | |
| { // No link, Display image tag without link tag around: | |
| $image_href = ''; | |
| } | |
| elseif( $slug_Item = & $ItemCache->get_by_urltitle( $href_match[2], false, false ) ) | |
| { // Use a link with item permanent url around image tag: | |
| $image_href = $slug_Item->get_permanent_url(); | |
| } | |
| else | |
| { // Wrong Item provided, Singal with special red class: | |
| $image_href = ''; | |
| $image_additional_class = 'imgerror'; | |
| } | |
| $image_rel = ''; // reset default attribute "rel" to don't display colorbox on click | |
| } | |
| $opt_index++; | |
| } | |
| $current_image_params['image_link_to'] = ( $image_href === false ? 'original' : $image_href ); | |
| $current_image_params['image_link_rel'] = $image_rel; | |
| // TODO: Size: | |
| // Class Name(s): | |
| $inline_param_class = ( empty( $inline_params[ $opt_index ] ) ? '' : $inline_params[ $opt_index ] ); | |
| if( $image_additional_class !== false ) | |
| { // Append additional class, e.g. on wrong provided item slug: | |
| $inline_param_class .= '.'.$image_additional_class; | |
| } | |
| if( ! empty( $inline_param_class ) ) | |
| { // A class name is set for the inline tags | |
| $image_extraclass = strip_tags( trim( str_replace( '.', ' ', $inline_param_class ) ) ); | |
| if( preg_match('#^[A-Za-z0-9\s\-_]+$#', $image_extraclass ) ) | |
| { | |
| if( $object_class == 'EmailCampaign' ) | |
| { // Append extra class to image/file inline img tags: | |
| $current_image_params['image_class'] = $image_extraclass; | |
| } | |
| else | |
| { // Inject extra class name(s) into 'before_image' param: | |
| $current_image_params['before_image'] = update_html_tag_attribs( $current_image_params['before_image'], array( 'class' => $image_extraclass ) ); | |
| } | |
| $current_file_params['class'] = $image_extraclass; | |
| } | |
| } | |
| } | |
| foreach( $current_image_params as $param_key => $param_value ) | |
| { // Pass all params by reference, in order to give possibility to modify them by plugin | |
| // So plugins can add some data before/after image tags (E.g. used by infodots plugin) | |
| $current_image_params[ $param_key ] = & $current_image_params[ $param_key ]; | |
| } | |
| // Prepare params before rendering attachment: | |
| $Plugins->trigger_event_first_true_with_params( $prepare_plugin_event_name, $current_image_params ); | |
| // Render attachments by plugin, Append the html content to $current_image_params['data'] and to $r: | |
| if( count( $Plugins->trigger_event_first_true_with_params( $render_plugin_event_name, $current_image_params ) ) != 0 ) | |
| { // This attachment has been rendered by a plugin (to $current_image_params['data']): | |
| $inlines[ $current_inline ] = $current_image_params['data']; | |
| break; | |
| } | |
| if( $inline_type == 'image' ) | |
| { // Generate the IMG tag with all the alt, title and desc if available: | |
| switch( $object_class ) | |
| { | |
| case 'Item': | |
| // Get the IMG tag with link to original image or to Item page: | |
| $inlines[ $current_inline ] = $Object->get_attached_image_tag( $Link, $current_image_params ); | |
| break; | |
| case 'EmailCampaign': | |
| // Get the IMG tag without link for email content: | |
| $image_style = ''; | |
| if( ! empty( $current_image_params['image_class'] ) ) | |
| { // Convert classes to style format: | |
| $image_style = emailskin_style( '.'.str_replace( ' ', '+.', $current_image_params['image_class'] ), false ); | |
| // We cannot use class attribute on email campaign content: | |
| unset( $current_image_params['image_class'] ); | |
| } | |
| $inlines[ $current_inline ] = $Link->get_tag( array_merge( $current_image_params, array( | |
| 'image_link_to' => $image_href, | |
| 'image_style' => 'border: none; max-width: 100%; height: auto;'.$image_style, | |
| 'add_loadimg' => false, | |
| ) ) ); | |
| break; | |
| default: | |
| // Get the IMG tag with link to original big image: | |
| $inlines[ $current_inline ] = $Link->get_tag( array_merge( $params, $current_image_params ) ); | |
| break; | |
| } | |
| } | |
| elseif( $inline_type == 'inline' ) | |
| { // Generate simple IMG tag with resized image size: | |
| switch( $object_class ) | |
| { | |
| case 'EmailCampaign': | |
| $image_style = ''; | |
| if( ! empty( $current_file_params['class'] ) ) | |
| { // Convert classes to style format: | |
| $image_style = emailskin_style( '.'.str_replace( ' ', '+.', $current_file_params['class'] ), false ); | |
| // We cannot use class attribute on email campaign content: | |
| unset( $current_file_params['class'] ); | |
| } | |
| $inlines[ $current_inline ] = $File->get_tag( '', '', '', '', $current_image_params['image_size'], '', '', '', | |
| '', '', $current_image_params['image_alt'], '', '', 1, NULL, 'border: none; max-width: 100%; height: auto;'.$image_style, false ); | |
| break; | |
| default: | |
| $inlines[ $current_inline ] = $File->get_tag( '', '', '', '', $current_image_params['image_size'], '', '', '', | |
| ( empty( $current_file_params['class'] ) ? '' : $current_file_params['class'] ), '', $current_image_params['image_alt'], '' ); | |
| } | |
| } | |
| } | |
| else | |
| { // not an image file, do not process | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| break; | |
| case 'thumbnail': | |
| if( $File->is_image() ) | |
| { | |
| global $thumbnail_sizes; | |
| $thumbnail_alt = ''; | |
| $thumbnail_href = false; | |
| $thumbnail_rel = NULL; | |
| $thumbnail_additional_class = false; | |
| $thumbnail_size = 'medium'; | |
| $thumbnail_position = 'left'; | |
| $thumbnail_classes = array(); | |
| if( ! empty( $inline[3] ) ) // check if second colon is present | |
| { | |
| // Get optional inline params: HRef, Size, Alignment, Class | |
| $inline_params = explode( ':', $inline[4] ); | |
| $opt_index = 0; | |
| // RegExp to detect HRef option: | |
| $href_regexp = '#^(https?|\(\((.*?)\)\))$#i'; | |
| // Alt text: | |
| if( isset( $inline_params[ $opt_index ] ) && | |
| substr( $inline_params[ $opt_index ], 0, 1 ) != '.' && | |
| ! preg_match( $href_regexp, $inline_params[ $opt_index ] ) && | |
| ! in_array( $inline_params[ $opt_index ], array( 'small', 'medium', 'large', 'left', 'right' ) ) ) | |
| { // Override the image File's alt text with provided in current inline tag: | |
| if( $inline_params[ $opt_index ] == '-' ) | |
| { // Alt text display is disabled: | |
| $thumbnail_alt = '-'; | |
| } | |
| else | |
| { // New image alt text was set: | |
| $thumbnail_alt = strip_tags( $inline_params[ $opt_index ] ); | |
| } | |
| $opt_index++; | |
| } | |
| // HRef: | |
| if( ! empty( $inline_params[ $opt_index ] ) && | |
| preg_match( $href_regexp, $inline_params[ $opt_index ], $href_match ) ) | |
| { | |
| if( stripos( $href_match[0], 'http' ) === 0 ) | |
| { // Absolute URL: | |
| $thumbnail_href = $href_match[0].':'.$inline_params[ $opt_index + 1 ]; | |
| $thumbnail_rel = ''; // reset default attribute "rel" to don't display colorbox on click | |
| $opt_index++; | |
| } | |
| else | |
| { // Item slug: | |
| $ItemCache = & get_ItemCache(); | |
| if( $href_match[2] === '' ) | |
| { // No link, Display image tag without link tag around: | |
| $thumbnail_href = ''; | |
| } | |
| elseif( $slug_Item = & $ItemCache->get_by_urltitle( $href_match[2], false, false ) ) | |
| { // Use a link with item permanent url around image tag: | |
| $thumbnail_href = $slug_Item->get_permanent_url(); | |
| } | |
| else | |
| { // Wrong Item provided, Singal with special red class: | |
| $thumbnail_href = ''; | |
| $thumbnail_additional_class = 'imgerror'; | |
| } | |
| $thumbnail_rel = ''; // reset default attribute "rel" to don't display colorbox on click | |
| } | |
| $opt_index++; | |
| } | |
| // Size: | |
| $valid_thumbnail_sizes = array( 'small', 'medium', 'large' ); | |
| if( ! empty( $inline_params[ $opt_index ] ) && in_array( $inline_params[ $opt_index ], $valid_thumbnail_sizes ) ) | |
| { | |
| $thumbnail_size = $inline_params[ $opt_index ]; | |
| $opt_index++; | |
| } | |
| // Alignment: | |
| $valid_thumbnail_positions = array( 'left', 'right' ); | |
| if( ! empty( $inline_params[ $opt_index ] ) && in_array( $inline_params[ $opt_index ], $valid_thumbnail_positions ) ) | |
| { | |
| $thumbnail_position = $inline_params[ $opt_index ]; | |
| $opt_index++; | |
| } | |
| // Class: | |
| $inline_param_class = ( empty( $inline_params[ $opt_index ] ) ? '' : $inline_params[ $opt_index ] ); | |
| if( $thumbnail_additional_class !== false ) | |
| { // Append additional class, e.g. on wrong provided item slug: | |
| $inline_param_class .= '.'.$thumbnail_additional_class; | |
| } | |
| if( ! empty( $inline_param_class ) ) | |
| { // A class name is set for the inline tags | |
| $extra_classes = explode( '.', ltrim( $inline_param_class, '.' ) ); | |
| } | |
| } | |
| switch( $thumbnail_size ) | |
| { | |
| case 'small': | |
| $thumbnail_size = 'fit-128x128'; | |
| break; | |
| case 'large': | |
| $thumbnail_size = 'fit-320x320'; | |
| break; | |
| case 'medium': | |
| default: | |
| $thumbnail_size = 'fit-192x192'; | |
| break; | |
| } | |
| $thumbnail_classes[] = 'evo_thumbnail'; | |
| $thumbnail_classes[] = 'evo_thumbnail__'.$thumbnail_position; | |
| if( isset( $extra_classes ) ) | |
| { | |
| $thumbnail_classes = array_merge( $thumbnail_classes, $extra_classes ); | |
| } | |
| $current_image_params = array( | |
| 'before_image' => '', | |
| 'before_image_legend' => '', // can be NULL | |
| 'after_image_legend' => '', | |
| 'after_image' => '', | |
| 'image_size' => $thumbnail_size, | |
| 'image_link_to' => ( $thumbnail_href === false ? 'original' : $thumbnail_href ), | |
| 'image_link_title' => '', // can be text or #title# or #desc# | |
| 'image_link_rel' => $thumbnail_rel, | |
| 'image_class' => implode( ' ', $thumbnail_classes ), | |
| 'image_alt' => $thumbnail_alt, | |
| ); | |
| switch( $object_class ) | |
| { | |
| case 'Item': | |
| // Get the IMG tag with link to original image or to Item page: | |
| $inlines[ $current_inline ] = $Object->get_attached_image_tag( $Link, $current_image_params ); | |
| break; | |
| case 'EmailCampaign': | |
| // Get the IMG tag without link for email content: | |
| $image_style = ''; | |
| if( ! empty( $current_image_params['image_class'] ) ) | |
| { // Convert classes to style format: | |
| $image_style = emailskin_style( '.'.str_replace( ' ', '+.', $current_image_params['image_class'] ), false ); | |
| // We cannot use class attribute on email campaign content: | |
| unset( $current_image_params['image_class'] ); | |
| } | |
| $inlines[ $current_inline ] = $Link->get_tag( array_merge( $current_image_params, array( | |
| 'image_link_to' => $thumbnail_href, | |
| 'image_style' => 'border: none; max-width: 100%; height: auto;'.$image_style, | |
| 'add_loadimg' => false, | |
| ) ) ); | |
| break; | |
| default: | |
| // Get the IMG tag with link to original big image: | |
| $inlines[ $current_inline ] = $Link->get_tag( array_merge( $params, $current_image_params ) ); | |
| break; | |
| } | |
| } | |
| else | |
| { // not an image file, do not process | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| break; | |
| case 'file': // valid file types: image, video, audio, other | |
| $valid_file_types = array( 'image', 'video', 'audio', 'other' ); | |
| if( in_array( $File->get_file_type(), $valid_file_types ) ) | |
| { | |
| if( ! empty( $inline[3] ) ) // check if second colon is present | |
| { | |
| // Get the file caption | |
| $caption = $inline[4]; | |
| if( ! empty( $caption ) ) | |
| { // Caption is set | |
| $current_file_params['title'] = strip_tags( $caption ); | |
| } | |
| } | |
| if( empty( $current_file_params['title'] ) ) | |
| { // Use real file name as title when it is not defined for inline tag | |
| $file_title = $File->get( 'title' ); | |
| $current_file_params['title'] = ' '.( empty( $file_title ) ? $File->get_name() : $file_title ); | |
| } | |
| elseif( $current_file_params['title'] == '-' ) | |
| { // Don't display a title in this case, Only file icon will be displayed | |
| $current_file_params['title'] = ''; | |
| } | |
| else | |
| { // Add a space between file icon and title | |
| $current_file_params['title'] = ' '.$current_file_params['title']; | |
| } | |
| $inlines[$current_inline] = '<a href="'.$File->get_url().'"' | |
| .( empty( $current_file_params['class'] ) ? '' : ' class="'.$current_file_params['class'].'"' ) | |
| .'>'.$File->get_icon( $current_file_params ).$current_file_params['title'].'</a>'; | |
| } | |
| else | |
| { // not a valid file type, do not process | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| break; | |
| case 'video': // valid file type: video | |
| if( $File->is_video() ) | |
| { | |
| $current_video_params = $params; | |
| // Create an empty dummy element where the plugin is expected to append the rendered video | |
| $current_video_params['data'] = ''; | |
| foreach( $current_video_params as $param_key => $param_value ) | |
| { // Pass all params by reference, in order to give possibility to modify them by plugin | |
| // So plugins can add some data before/after tags (E.g. used by infodots plugin) | |
| $current_video_params[ $param_key ] = & $current_video_params[ $param_key ]; | |
| } | |
| // Prepare params before rendering attachment: | |
| $Plugins->trigger_event_first_true_with_params( $prepare_plugin_event_name, $current_video_params ); | |
| // Render attachments by plugin: | |
| if( count( $Plugins->trigger_event_first_true_with_params( $render_plugin_event_name, $current_video_params ) ) != 0 ) | |
| { // This attachment has been rendered by a plugin (to $current_video_params['data']): | |
| $inlines[$current_inline] = $current_video_params['data']; | |
| } | |
| else | |
| { // no plugin available or was able to render the tag | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| } | |
| else | |
| { // not a video file, do not process | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| break; | |
| case 'audio': // valid file type: audio | |
| if( $File->is_audio() ) | |
| { | |
| $current_audio_params = $params; | |
| // Create an empty dummy element where the plugin is expected to append the rendered video | |
| $current_audio_params['data'] = ''; | |
| foreach( $current_audio_params as $param_key => $param_value ) | |
| { // Pass all params by reference, in order to give possibility to modify them by plugin | |
| // So plugins can add some data before/after tags (E.g. used by infodots plugin) | |
| $current_audio_params[ $param_key ] = & $current_audio_params[ $param_key ]; | |
| } | |
| // Prepare params before rendering attachment: | |
| $Plugins->trigger_event_first_true_with_params( $prepare_plugin_event_name, $current_audio_params ); | |
| // Render attachments by plugin: | |
| if( count( $Plugins->trigger_event_first_true_with_params( $render_plugin_event_name, $current_audio_params ) ) != 0 ) | |
| { // This attachment has been rendered by a plugin (to $current_audio_params['data']): | |
| $inlines[$current_inline] = $current_audio_params['data']; | |
| } | |
| else | |
| { // no plugin available or was able to render the tag | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| } | |
| else | |
| { // not a video file, do not process | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| break; | |
| case 'folder': | |
| if( $File->is_dir() ) | |
| { | |
| $current_folder_params = $params; | |
| if( ! empty( $inline[3] ) ) // check if second colon is present | |
| { | |
| if( preg_match( '/^\d+$/', $inline[4] ) ) | |
| { // limit number of images | |
| $current_folder_params['gallery_image_limit'] = (int) $inline[4]; | |
| } | |
| } | |
| $inlines[$current_inline] = $File->get_gallery( $current_folder_params ); | |
| } | |
| else | |
| { | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| break; | |
| default: | |
| $inlines[$current_inline] = $current_inline; | |
| } | |
| } | |
| return $inlines; | |
| } | |
| /** | |
| * Convert date format from locale for jQuery datepicker plugin | |
| * | |
| * @param string Date format of locale from DB; for example: Y-m-d | |
| * @return string Date format for jQuery datepicker plugin; for example: yy-mm-dd | |
| */ | |
| function php_to_jquery_date_format( $php_format ) | |
| { | |
| $tokens = array( | |
| // Day | |
| 'd' => 'dd', | |
| 'D' => 'D', | |
| 'j' => 'd', | |
| 'l' => 'DD', | |
| 'N' => '', | |
| 'S' => '', | |
| 'w' => '', | |
| 'z' => 'o', | |
| // Week | |
| 'W' => '', | |
| // Month | |
| 'F' => 'MM', | |
| 'm' => 'mm', | |
| 'M' => 'M', | |
| 'n' => 'm', | |
| 't' => '', | |
| // Year | |
| 'L' => '', | |
| 'o' => '', | |
| 'Y' => 'yy', | |
| 'y' => 'y', | |
| // Time | |
| 'a' => '', | |
| 'A' => '', | |
| 'B' => '', | |
| 'g' => '', | |
| 'G' => '', | |
| 'h' => '', | |
| 'H' => '', | |
| 'i' => '', | |
| 's' => '', | |
| 'u' => '' ); | |
| $js_format = ""; | |
| $escaping = false; | |
| for( $i = 0; $i < strlen( $php_format ); $i++ ) | |
| { | |
| $char = $php_format[$i]; | |
| if($char === '\\') // PHP date format escaping character | |
| { | |
| $i++; | |
| if( $escaping ) $js_format .= $php_format[$i]; | |
| else $js_format .= '\'' . $php_format[$i]; | |
| $escaping = true; | |
| } | |
| else | |
| { | |
| if( $escaping ) | |
| { | |
| $jqueryui_format .= "'"; $escaping = false; | |
| } | |
| if( isset( $tokens[$char] ) ) | |
| { | |
| $js_format .= $tokens[$char]; | |
| } | |
| else | |
| { | |
| $js_format .= $char; | |
| } | |
| } | |
| } | |
| return $js_format; | |
| } | |
| /** | |
| * Check if given string is HTML | |
| * | |
| * @param string String to check if HTML | |
| * @return boolean True if string is HTML | |
| */ | |
| function is_html( $string ) | |
| { | |
| return $string != strip_tags( $string ) ? true : false; | |
| } | |
| /** | |
| * Get date format from current locale for jQuery datepicker plugin | |
| * | |
| * @return string Date format; for example: yy-mm-dd | |
| */ | |
| function jquery_datepicker_datefmt() | |
| { | |
| return php_to_jquery_date_format( locale_input_datefmt() ); | |
| } | |
| /** | |
| * Get month names as string of JavaScript array for jQuery datepicker plugin | |
| * | |
| * @return string | |
| */ | |
| function jquery_datepicker_month_names() | |
| { | |
| $months = array( | |
| TS_('January'), | |
| TS_('February'), | |
| TS_('March'), | |
| TS_('April'), | |
| TS_('May'), | |
| TS_('June'), | |
| TS_('July'), | |
| TS_('August'), | |
| TS_('September'), | |
| TS_('October'), | |
| TS_('November'), | |
| TS_('December') | |
| ); | |
| return '[\''.implode( '\', \'', $months ).'\']'; | |
| } | |
| /** | |
| * Get week day names as string of JavaScript array for jQuery datepicker plugin | |
| * | |
| * @return string | |
| */ | |
| function jquery_datepicker_day_names() | |
| { | |
| $days = array( | |
| TS_('Sun'), | |
| TS_('Mon'), | |
| TS_('Tue'), | |
| TS_('Wed'), | |
| TS_('Thu'), | |
| TS_('Fri'), | |
| TS_('Sat') | |
| ); | |
| foreach( $days as $d => $day ) | |
| { | |
| $days[ $d ] = utf8_substr( $day, 0, 2 ); | |
| } | |
| return '[\''.implode( '\', \'', $days ).'\']'; | |
| } | |
| /** | |
| * Find the dates without data and fill them with 0 to display on graph and table | |
| * | |
| * @param array Source data | |
| * @param array Default data, e.g. array( 'hits' => 0 ) | |
| * @param string Start date of log in format 'YYYY-mm-dd' | |
| * @param string End date of log in format 'YYYY-mm-dd' | |
| * @return array Fixed data | |
| */ | |
| function fill_empty_days( $data, $default_data, $start_date, $end_date ) | |
| { | |
| $fixed_data = array(); | |
| $start_date = date( 'Y-n-j', strtotime( $start_date) ); | |
| $end_date = date( 'Y-n-j', strtotime( $end_date) ); | |
| if( empty( $data ) ) | |
| { | |
| return $fixed_data; | |
| } | |
| // Get additional fields which must be exist in each array item of new filled empty day below: | |
| $additional_fields = array_diff_key( $data[0], array( 'year' => 0, 'month' => 0, 'day' => 0 ) ); | |
| // Check if data array contains start and end dates: | |
| $start_date_is_contained = empty( $start_date ); | |
| $end_date_is_contained = empty( $end_date ); | |
| if( ! $start_date_is_contained || ! $end_date_is_contained ) | |
| { | |
| foreach( $data as $row ) | |
| { | |
| $this_date = $row['year'].'-'.$row['month'].'-'.$row['day']; | |
| if( $this_date == $start_date ) | |
| { // The start date is detected: | |
| $start_date_is_contained = true; | |
| } | |
| if( $this_date == $end_date ) | |
| { // The start date is detected: | |
| $end_date_is_contained = true; | |
| } | |
| if( $start_date_is_contained && $end_date_is_contained ) | |
| { // Stop array searching here because we have found the dates: | |
| break; | |
| } | |
| } | |
| } | |
| if( ! $start_date_is_contained ) | |
| { // Add item to array with 0 for start date if stats has no data for the date: | |
| array_push( $data, array_merge( array( | |
| 'year' => date( 'Y', strtotime( $start_date ) ), | |
| 'month' => date( 'n', strtotime( $start_date ) ), | |
| 'day' => date( 'j', strtotime( $start_date ) ), | |
| ), $default_data ) + $additional_fields ); | |
| } | |
| if( ! $end_date_is_contained ) | |
| { // Add item to array with 0 for end date if stats has no data for the date: | |
| array_unshift( $data, array_merge( array( | |
| 'year' => date( 'Y', strtotime( $end_date ) ), | |
| 'month' => date( 'n', strtotime( $end_date ) ), | |
| 'day' => date( 'j', strtotime( $end_date ) ), | |
| ), $default_data ) + $additional_fields ); | |
| } | |
| foreach( $data as $row ) | |
| { | |
| $this_date = $row['year'].'-'.$row['month'].'-'.$row['day']; | |
| if( isset( $prev_date ) && $prev_date != $this_date ) | |
| { // If data are from another day: | |
| $prev_time = strtotime( $prev_date ) - 86400; | |
| $this_time = strtotime( $this_date ); | |
| if( $prev_time != $this_time ) | |
| { // If previous date is not previous day(it means some day has no data): | |
| $empty_days = ( $prev_time - $this_time ) / 86400; | |
| for( $d = 0; $d < $empty_days; $d++ ) | |
| { // Add each empty day to array with default data: | |
| $empty_day = $prev_time - $d * 86400; | |
| $fixed_data[] = array_merge( array( | |
| 'year' => date( 'Y', $empty_day ), | |
| 'month' => date( 'n', $empty_day ), | |
| 'day' => date( 'j', $empty_day ), | |
| ), $default_data ) + $additional_fields; | |
| } | |
| } | |
| } | |
| $prev_date = $row['year'].'-'.$row['month'].'-'.$row['day']; | |
| $fixed_data[] = $row; | |
| } | |
| return $fixed_data; | |
| } | |
| /** | |
| * Get image file used for social media | |
| * | |
| * @param object Item object | |
| * @param array Params | |
| * @return object Image File or Link object | |
| */ | |
| function get_social_media_image( $Item = NULL, $params = array() ) | |
| { | |
| $params = array_merge( array( | |
| 'use_item_cat_fallback' => true, | |
| 'use_coll_fallback' => true, | |
| 'use_site_fallback' => true, | |
| 'return_as_link' => false, | |
| ), $params ); | |
| $social_media_image = NULL; | |
| if( ! empty( $Item ) ) | |
| { // Try to get attached images | |
| $LinkOwner = new LinkItem( $Item ); | |
| if( $LinkList = $LinkOwner->get_attachment_LinkList( 1000, 'cover,background,teaser,teaserperm,teaserlink', 'image', array( | |
| 'sql_select_add' => ', CASE WHEN link_position = "cover" THEN 1 WHEN link_position IN ( "teaser", "teaserperm", "teaserlink" ) THEN 2 ELSE 3 END AS link_priority', | |
| 'sql_order_by' => 'link_priority ASC, link_order ASC' ) ) ) | |
| { // Item has linked files: | |
| while( $Link = & $LinkList->get_next() ) | |
| { | |
| if( ! ( $File = & $Link->get_File() ) ) | |
| { // No File object: | |
| global $Debuglog; | |
| $Debuglog->add( sprintf( 'Link ID#%d of item #%d does not have a file object!', $Link->ID, $Item->ID ), array( 'error', 'files' ) ); | |
| continue; | |
| } | |
| if( ! $File->exists() ) | |
| { // File doesn't exist: | |
| global $Debuglog; | |
| $Debuglog->add( sprintf( 'File linked to item #%d does not exist (%s)!', $Item->ID, $File->get_full_path() ), array( 'error', 'files' ) ); | |
| continue; | |
| } | |
| if( $File->is_image() ) | |
| { // Use only image files for og:image tag: | |
| if( $params['return_as_link'] ) | |
| { | |
| return $Link; | |
| } | |
| else | |
| { | |
| return $File; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| if( $params['use_item_cat_fallback'] ) | |
| { // No attached image from Item, let's try getting one from the Item's default chapter | |
| $FileCache = & get_FileCache(); | |
| if( $default_Chapter = & $Item->get_main_Chapter() ) | |
| { // Try social media boilerplate image first: | |
| $social_media_image_file_ID = $default_Chapter->get( 'social_media_image_file_ID', false ); | |
| if( $social_media_image_file_ID > 0 && ( $File = & $FileCache->get_by_ID( $social_media_image_file_ID ) ) && $File->is_image() ) | |
| { | |
| return $File; | |
| } | |
| } | |
| } | |
| } | |
| global $Blog; | |
| if( $params['use_coll_fallback'] && $Blog ) | |
| { // Try to get collection social media boiler plate and collection image/logo: | |
| $FileCache = & get_FileCache(); | |
| $social_media_image_file_ID = $Blog->get_setting( 'social_media_image_file_ID', false ); | |
| if( $social_media_image_file_ID > 0 && ( $File = & $FileCache->get_by_ID( $social_media_image_file_ID ) ) && $File->is_image() ) | |
| { // Try social media boiler plate first: | |
| return $File; | |
| } | |
| } | |
| if( $params['use_site_fallback'] ) | |
| { // Use social media boilerplate logo if configured | |
| global $Settings; | |
| $FileCache = & get_FileCache(); | |
| $social_media_image_file_ID = intval( $Settings->get( 'social_media_image_file_ID' ) ); | |
| if( $social_media_image_file_ID > 0 && ( $File = $FileCache->get_by_ID( $social_media_image_file_ID, false ) ) && $File->is_image() ) | |
| { | |
| return $File; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * Opens modal to insert inline image tags. | |
| * Used by the following plugins: | |
| * - evo_TinyMCE | |
| * - evo_inlines | |
| * | |
| * @param array Params | |
| */ | |
| function insert_image_links_block( $params ) | |
| { | |
| global $current_User, $inc_path, $Blog, $blog, $LinkOwner; | |
| global $is_admin_page; | |
| load_funcs( 'links/model/_link.funcs.php' ); | |
| $params = array_merge( array( | |
| 'target_type' => NULL, | |
| ), $params ); | |
| if( ! empty( $params['blog'] ) ) | |
| { | |
| $BlogCache = & get_BlogCache(); | |
| $blog = $params['blog']; | |
| $Blog = $BlogCache->get_by_ID( $blog ); | |
| } | |
| $temp_ID = empty( $params['temp_ID'] ) ? NULL : $params['temp_ID']; | |
| $is_admin_page = is_logged_in() && isset( $params['request_from'] ) && ( $params['request_from'] == 'back' ); | |
| switch( $params['target_type'] ) | |
| { | |
| case 'Item': | |
| if( ! isset( $params['target_ID'] ) && ! isset( $params['temp_ID'] ) ) | |
| { | |
| return; | |
| } | |
| $ItemCache = & get_ItemCache(); | |
| $edited_Item = & $ItemCache->get_by_ID( $params['target_ID'], false, false ); | |
| if( empty( $blog ) && $edited_Item ) | |
| { | |
| $Blog = $edited_Item->get_Blog(); | |
| $blog = $Blog->ID; | |
| } | |
| if( isset( $GLOBALS['files_Module'] ) | |
| && ( ( $edited_Item && check_user_perm( 'item_post!CURSTATUS', 'edit', false, $edited_Item ) ) || ( empty( $edited_Item ) && $params['temp_ID'] ) ) | |
| && check_user_perm( 'files', 'view', false ) ) | |
| { // Files module is enabled, but in case of creating new posts we should show file attachments block only if user has all required permissions to attach files | |
| load_class( 'links/model/_linkitem.class.php', 'LinkItem' ); | |
| global $LinkOwner; // Initialize this object as global because this is used in many link functions | |
| $LinkOwner = new LinkItem( $edited_Item, $temp_ID ); | |
| } | |
| break; | |
| case 'Comment': | |
| if( ! isset( $params['target_ID'] ) ) | |
| { | |
| return; | |
| } | |
| $CommentCache = & get_CommentCache(); | |
| $edited_Comment = & $CommentCache->get_by_ID( $params['target_ID'] ); | |
| $comment_Item = & $edited_Comment->get_Item(); | |
| if( empty( $blog ) && $comment_Item ) | |
| { | |
| $Blog = $comment_Item->get_Blog(); | |
| $blog = $Blog->ID; | |
| } | |
| if( isset( $GLOBALS['files_Module'] ) | |
| && check_user_perm( 'comment!CURSTATUS', 'edit', false, $edited_Comment ) | |
| && check_user_perm( 'files', 'view', false ) ) | |
| { // Files module is enabled, but in case of creating new comments we should show file attachments block only if user has all required permissions to attach files | |
| load_class( 'links/model/_linkcomment.class.php', 'LinkComment' ); | |
| global $LinkOwner; // Initialize this object as global because this is used in many link functions | |
| $LinkOwner = new LinkComment( $edited_Comment ); | |
| } | |
| break; | |
| case 'EmailCampaign': | |
| if( ! isset( $params['target_ID'] ) ) | |
| { | |
| return; | |
| } | |
| $EmailCampaignCache = & get_EmailCampaignCache(); | |
| $edited_EmailCampaign = $EmailCampaignCache->get_by_ID( $params['target_ID'] ); | |
| if( isset( $GLOBALS['files_Module'] ) | |
| && check_user_perm( 'emails', 'edit', false ) | |
| && check_user_perm( 'files', 'view', false ) ) | |
| { // Files module is enabled, but in case of creating new email campaign we should show file attachments block only if user has all required permissions to attach files | |
| load_class( 'links/model/_linkemailcampaign.class.php', 'LinkEmailCampaign' ); | |
| global $LinkOwner; // Initialize this object as global because this is used in many link functions | |
| $LinkOwner = new LinkEmailCampaign( $edited_EmailCampaign ); | |
| } | |
| break; | |
| case 'Message': | |
| if( ! isset( $params['target_ID'] ) && ! isset( $params['temp_ID'] ) ) | |
| { | |
| return; | |
| } | |
| $MessageCache = & get_MessageCache(); | |
| $edited_Message = $MessageCache->get_by_ID( $params['target_ID'], false, false ); | |
| if( isset( $GLOBALS['files_Module'] ) | |
| && check_user_perm( 'perm_messaging', 'reply' ) | |
| && check_user_perm( 'files', 'view', false ) ) | |
| { // Files module is enabled, but in case of creating new messages we should show file attachments block only if user has all required permissions to attach files | |
| load_class( 'links/model/_linkmessage.class.php', 'LinkMessage' ); | |
| global $LinkOwner; // Initialize this object as global because this is used in many link functions | |
| $LinkOwner = new LinkMessage( $edited_Message, $temp_ID ); | |
| } | |
| break; | |
| default: | |
| return; | |
| } | |
| global $fm_mode; | |
| $fm_mode = 'file_select'; | |
| // Set a different drag and drop fieldset prefix: | |
| $fieldset_prefix = 'modal_'; | |
| if( is_admin_page() ) | |
| { | |
| global $UserSettings, $adminskins_path, $AdminUI; | |
| $admin_skin = $UserSettings->get( 'admin_skin', $current_User->ID ); | |
| require_once $adminskins_path.$admin_skin.'/_adminUI.class.php'; | |
| $AdminUI = new AdminUI(); | |
| } | |
| else | |
| { | |
| global $Skin, $inc_path; | |
| init_fontawesome_icons(); | |
| $blog_skin_ID = $Blog->get_skin_ID(); | |
| $SkinCache = & get_SkinCache(); | |
| $Skin = & $SkinCache->get_by_ID( $blog_skin_ID ); | |
| } | |
| require $inc_path.'links/views/_link_list.view.php'; | |
| } | |
| /** | |
| * Get line for CSV file from provided array | |
| * | |
| * @param array Row data | |
| * @param string Delimiter | |
| * @param string Enclosure | |
| * @param string End of line | |
| * @return string | |
| */ | |
| function get_csv_line( $row, $delimiter = ';', $enclosure = '"', $eol = "\n" ) | |
| { | |
| foreach( $row as $r => $cell ) | |
| { | |
| $row[ $r ] = str_replace( $enclosure, $enclosure.$enclosure, $cell ); | |
| if( strpos( $cell, $delimiter ) !== false ) | |
| { | |
| $row[ $r ] = $enclosure.$row[ $r ].$enclosure; | |
| } | |
| } | |
| return implode( $delimiter, $row ).$eol; | |
| } | |
| /** | |
| * Display a panel to upload files before import | |
| * | |
| * @param array Params | |
| * @return array Already uploaded files in the requested folder | |
| */ | |
| function display_importer_upload_panel( $params = array() ) | |
| { | |
| global $admin_url, $media_path; | |
| $params = array_merge( array( | |
| 'folder' => '', | |
| 'allowed_extensions' => 'csv', // Allowed extensions to import, separated by | | |
| 'infolder_extensions' => false, // Allowed extensions inside folders, separated by |, FALSE - to don't find files in subfolders | |
| 'folder_with_extensions' => false, // Find folders which contain at least one file with extensions(separated by |) in subfolders recursively | |
| 'find_attachments' => false, | |
| 'display_type' => false, | |
| 'help_slug' => '', | |
| 'refresh_url' => '', | |
| ), $params ); | |
| evo_flush(); | |
| // Get available files to import from the folder /media/import/ | |
| $import_files = get_import_files( $params['folder'], $params['allowed_extensions'], $params['infolder_extensions'], $params['find_attachments'], $params['folder_with_extensions'] ); | |
| load_class( '_core/ui/_table.class.php', 'Table' ); | |
| $Table = new Table( NULL, 'import' ); | |
| $Table->cols = array(); | |
| $Table->cols[] = array( 'th' => T_('Import'), 'td_class' => 'shrinkwrap' ); | |
| $Table->cols[] = array( 'th' => T_('File') ); | |
| if( $params['display_type'] ) | |
| { // Display file type: | |
| $Table->cols[] = array( 'th' => T_('Type') ); | |
| } | |
| $Table->cols[] = array( 'th' => T_('Date'), 'td_class' => 'shrinkwrap' ); | |
| // Get link to manual page: | |
| $manual_link = ( empty( $params['help_slug'] ) ? '' : get_manual_link( $params['help_slug'] ) ); | |
| $Table->title = T_('Potential files to be imported').$manual_link; | |
| if( ! empty( $params['refresh_url'] ) ) | |
| { // Display a link to refresh the uploaded files: | |
| $Table->title .= ' - '.action_icon( T_('Refresh'), 'refresh', $params['refresh_url'], T_('Refresh'), 3, 4 ); | |
| } | |
| $FileRootCache = & get_FileRootCache(); | |
| $FileRoot = & $FileRootCache->get_by_type_and_ID( 'import', '0', true ); | |
| $import_perm_view = check_user_perm( 'files', 'view', false, $FileRoot ); | |
| if( $import_perm_view ) | |
| { // Current user must has access to the import dir | |
| if( check_user_perm( 'files', 'edit_allowed', false, $FileRoot ) ) | |
| { // User has full access | |
| $import_title = T_('Upload/Manage import files'); | |
| } | |
| else if( check_user_perm( 'files', 'add', false, $FileRoot ) ) | |
| { // User can only upload the files to import root | |
| $import_title = T_('Upload import files'); | |
| } | |
| else | |
| { // Only view | |
| $import_title = T_('View import files'); | |
| } | |
| $Table->title .= ' - ' | |
| .action_icon( $import_title, 'folder', $admin_url.'?ctrl=files&root=import_0&path='.$params['folder'], $import_title, 3, 4, | |
| array( 'onclick' => 'return import_files_window()' ) | |
| ).' <span class="note">(popup)</span>'; | |
| } | |
| $Table->display_init(); | |
| echo $Table->params['before']; | |
| // TITLE: | |
| $Table->display_head(); | |
| if( empty( $import_files ) ) | |
| { // No files to import: | |
| $Table->total_pages = 0; | |
| $Table->no_results_text = '<div class="center">'.T_('We have not found any suitable file to perform the import. Please read the details at the manual page.').$manual_link.'</div>'; | |
| // BODY START: | |
| $Table->display_body_start(); | |
| $Table->display_list_start(); | |
| $Table->display_list_end(); | |
| // BODY END: | |
| $Table->display_body_end(); | |
| } | |
| else | |
| { // Display the files to import in table: | |
| // TABLE START: | |
| $Table->display_list_start(); | |
| // COLUMN HEADERS: | |
| $Table->display_col_headers(); | |
| // BODY START: | |
| $Table->display_body_start(); | |
| foreach( $import_files as $import_file ) | |
| { | |
| $Table->display_line_start(); | |
| // Checkbox to import | |
| $Table->display_col_start(); | |
| echo '<input type="radio" name="import_file" value="'.$import_file['path'].'"'.( get_param( 'import_file' ) == $import_file['path'] ? ' checked="checked"' : '' ).' />'; | |
| $Table->display_col_end(); | |
| // File | |
| $Table->display_col_start(); | |
| echo $import_file['name']; | |
| $Table->display_col_end(); | |
| // Type | |
| if( $params['display_type'] ) | |
| { // Display file type: | |
| $Table->display_col_start(); | |
| echo $import_file['type']; | |
| $Table->display_col_end(); | |
| } | |
| // File date | |
| $Table->display_col_start(); | |
| echo date( locale_datefmt().' '.locale_timefmt(), $import_file['date'] ); | |
| $Table->display_col_end(); | |
| $Table->display_line_end(); | |
| evo_flush(); | |
| } | |
| // BODY END: | |
| $Table->display_body_end(); | |
| // TABLE END: | |
| $Table->display_list_end(); | |
| } | |
| echo $Table->params['after']; | |
| ?> | |
| <script> | |
| jQuery( '.table_scroll td' ).on( 'click', function() | |
| { | |
| jQuery( this ).parent().find( 'input[type=radio]' ).prop( 'checked', true ); | |
| } ); | |
| </script> | |
| <?php | |
| if( $import_perm_view ) | |
| { // Current user must has access to the import dir: | |
| // Initialize JavaScript to build and open window: | |
| echo_modalwindow_js(); | |
| ?> | |
| <script> | |
| function import_files_window() | |
| { | |
| openModalWindow( '<span class="loader_img absolute_center" title="<?php echo T_('Loading...'); ?>"></span>', | |
| '90%', '80%', true, '<?php echo TS_('Add/Link files'); ?>', '', true ); | |
| jQuery.ajax( | |
| { | |
| type: 'POST', | |
| url: '<?php echo get_htsrv_url(); ?>async.php', | |
| data: | |
| { | |
| 'action': 'import_files', | |
| 'path': '<?php echo $params['folder']; ?>', | |
| 'crumb_import': '<?php echo get_crumb( 'import' ); ?>', | |
| }, | |
| success: function( result ) | |
| { | |
| openModalWindow( result, '90%', '80%', true, '<?php echo TS_('Upload/Manage import files'); ?>', '' ); | |
| } | |
| } ); | |
| return false; | |
| } | |
| jQuery( document ).on( 'click', '#modal_window button[data-dismiss=modal]', function() | |
| { // Reload page on closing modal window to display new uploaded files: | |
| location.reload(); | |
| } ); | |
| </script> | |
| <?php | |
| } | |
| return $import_files; | |
| } | |
| /** | |
| * Get available files to import from the requested folder | |
| * | |
| * @param string Sub folder in the folder /media/import/ | |
| * @param string Allowed extensions to import, separated by | | |
| * @param string|boolean Allowed extensions inside folders, separated by |, FALSE - to don't find files in subfolders | |
| * @param boolean TRUE - to find folder of attachments | |
| * @param string|boolean Find folders which contain at least one file with extensions(separated by |) recursively in all subfolders | |
| * @return array Files | |
| */ | |
| function get_import_files( $folder = '', $allowed_extensions = 'xml|txt|zip', $infolder_extensions = 'xml|txt', $find_attachments = true, $folder_with_extensions = false ) | |
| { | |
| global $media_path; | |
| // Get all files from the import folder: | |
| $root_path = $media_path.'import/'.( empty( $folder ) ? '' : $folder.'/' ); | |
| $files = get_filenames( $root_path, array( | |
| 'flat' => false | |
| ) ); | |
| $import_files = array(); | |
| if( empty( $files ) ) | |
| { // No access to the import folder OR it is empty | |
| return $import_files; | |
| } | |
| $file_paths = array(); | |
| foreach( $files as $folder_name => $file ) | |
| { | |
| if( is_array( $file ) ) | |
| { // It is a folder | |
| if( $infolder_extensions !== false ) | |
| { // Find files inside: | |
| foreach( $file as $key => $sub_file ) | |
| { | |
| if( is_string( $sub_file ) && preg_match( '/\.('.$infolder_extensions.')$/i', $sub_file, $file_matches ) ) | |
| { | |
| $file_paths[] = array( $sub_file, $file_matches[1] ); | |
| } | |
| } | |
| } | |
| if( $folder_with_extensions !== false && check_folder_with_extensions( $root_path.$folder_name, $folder_with_extensions ) ) | |
| { // Use full folder as single import pack when it contains file with requested extensions: | |
| $file_paths[] = array( $root_path.$folder_name, '$dir$' ); | |
| } | |
| } | |
| elseif( is_string( $file ) && preg_match( '/\.('.$allowed_extensions.')$/i', $file, $file_matches ) ) | |
| { // File in the root: | |
| $file_paths[] = array( $file, $file_matches[1] ); | |
| } | |
| } | |
| $media_path_length = strlen( $media_path.'import/'.( empty( $folder ) ? '' : $folder.'/' ) ); | |
| foreach( $file_paths as $file_data ) | |
| { | |
| switch( $file_data[1] ) | |
| { | |
| case '$dir$': | |
| $file_type = T_('Folder'); | |
| break; | |
| case 'zip': | |
| $file_type = T_('Compressed Archive'); | |
| break; | |
| default: | |
| if( $find_attachments && ( $file_attachments_folder = get_import_attachments_folder( $file_data[0] ) ) ) | |
| { // Probably it is a file with attachments folder: | |
| $file_type = sprintf( T_('Complete export (attachments folder: %s)'), '<code>'.substr( $file_attachments_folder, strlen( dirname( $file_data[0] ) ) + 1, -1 ).'</code>' ); | |
| } | |
| else | |
| { // Single XML file without attachments folder: | |
| $file_type = T_('Basic export').( $find_attachments ? ' ('.T_('no attachments folder found').')' : '' ); | |
| } | |
| break; | |
| } | |
| $import_files[] = array( | |
| 'path' => $file_data[0], | |
| 'name' => substr( $file_data[0], $media_path_length ), | |
| 'type' => $file_type, | |
| 'date' => filemtime( $file_data[0] ), | |
| ); | |
| } | |
| // Sort import files by date DESC: | |
| usort( $import_files, 'sort_import_files_callback' ); | |
| return $import_files; | |
| } | |
| /** | |
| * Callback function to sort import files by date DESC | |
| * | |
| * @param array Import file data | |
| * @param array Import file data | |
| * @return boolean | |
| */ | |
| function sort_import_files_callback( $a, $b ) | |
| { | |
| if( $a['date'] == $b['date'] ) | |
| { // Sort by file name with same dates: | |
| return $a['name'] < $b['name'] ? -1 : 1; | |
| } | |
| return ( $a['date'] > $b['date'] ? -1 : 1 ); | |
| } | |
| /** | |
| * Find attachments folder path for given import file path | |
| * | |
| * @param string File path | |
| * @param boolean TRUE to use first found folder if no reserved folders not found before | |
| * @return string Folder path | |
| */ | |
| function get_import_attachments_folder( $file_path, $first_folder = false ) | |
| { | |
| $file_name = basename( $file_path ); | |
| $file_folder_path = dirname( $file_path ).'/'; | |
| $folder_full_name = preg_replace( '#\.[^\.]+$#', '', $file_name ); | |
| $folder_part_name = preg_replace( '#_[^_]+$#', '', $folder_full_name ); | |
| // Find and get first existing folder with attachments: | |
| if( is_dir( $file_folder_path.$folder_full_name ) ) | |
| { // 1st priority folder: | |
| return $file_folder_path.$folder_full_name.'/'; | |
| } | |
| if( is_dir( $file_folder_path.$folder_part_name.'_files' ) ) | |
| { // 2nd priority folder: | |
| return $file_folder_path.$folder_part_name.'_files/'; | |
| } | |
| if( is_dir( $file_folder_path.$folder_part_name.'_attachments' ) ) | |
| { // 3rd priority folder: | |
| return $file_folder_path.$folder_part_name.'_attachments/'; | |
| } | |
| if( is_dir( $file_folder_path.'b2evolution_export_files' ) ) | |
| { // 4th priority folder: | |
| return $file_folder_path.'b2evolution_export_files/'; | |
| } | |
| if( is_dir( $file_folder_path.'export_files' ) ) | |
| { // 5th priority folder: | |
| return $file_folder_path.'export_files/'; | |
| } | |
| if( is_dir( $file_folder_path.'import_files' ) ) | |
| { // 6th priority folder: | |
| return $file_folder_path.'import_files/'; | |
| } | |
| if( is_dir( $file_folder_path.'files' ) ) | |
| { // 7th priority folder: | |
| return $file_folder_path.'files/'; | |
| } | |
| if( is_dir( $file_folder_path.'attachments' ) ) | |
| { // 8th priority folder: | |
| return $file_folder_path.'attachments/'; | |
| } | |
| if( is_dir( $file_folder_path.'uploads' ) ) | |
| { // 9th priority folder: | |
| return $file_folder_path.'uploads/'; | |
| } | |
| if( is_dir( $file_folder_path.'wp-content/uploads' ) ) | |
| { // 10th priority folder: | |
| return $file_folder_path.'wp-content/uploads/'; | |
| } | |
| if( $first_folder ) | |
| { // Try to use first found folder: | |
| $files = scandir( $file_folder_path ); | |
| foreach( $files as $file ) | |
| { | |
| if( $file == '.' || $file == '..' ) | |
| { // Skip reserved dir names of the current path: | |
| continue; | |
| } | |
| if( is_dir( $file_folder_path.$file ) ) | |
| { // 11th priority folder: | |
| return $file_folder_path.$file.'/'; | |
| } | |
| } | |
| } | |
| // File has no attachments folder | |
| return false; | |
| } | |
| /** | |
| * Clear string list of IDs to exclude wrong not ID/number/integer value | |
| * | |
| * @param string ID values separated by second param | |
| * @param string Separator | |
| * @return string Fixed list | |
| */ | |
| function clear_ids_list( $ids_list, $separator = ',' ) | |
| { | |
| if( $ids_list === '' ) | |
| { // Empty list: | |
| return $ids_list; | |
| } | |
| if( strpos( $ids_list, '-' ) === 0 ) | |
| { // Remove first '-' char from start, which is used for exluding list: | |
| $ids_list = substr( $ids_list, 1 ); | |
| } | |
| $ids_list = explode( $separator, $ids_list ); | |
| foreach( $ids_list as $i => $ID ) | |
| { | |
| if( ! is_number( $ID ) ) | |
| { // Remove a not number value from list: | |
| unset( $ids_list[ $i ] ); | |
| } | |
| } | |
| return implode( $separator, $ids_list ); | |
| } | |
| /** | |
| * Check if the given allowed | |
| * | |
| * @param string The options which should be checked | |
| * @param string String of options separated by comma, '*' - allow all options, use '-' before options if they should be excluded/denied | |
| * @return boolean | |
| */ | |
| function is_allowed_option( $checked_option, $allowed_options ) | |
| { | |
| if( $allowed_options === '*' ) | |
| { // All options are allowed: | |
| return true; | |
| } | |
| $is_allowed_options = true; | |
| $allowed_options = $allowed_options; | |
| if( substr( $allowed_options, 0, 1 ) == '-' ) | |
| { // The options should excluded/denied: | |
| $allowed_options = substr( $allowed_options, 1 ); | |
| $is_allowed_options = false; | |
| } | |
| $allowed_options = explode( ',', $allowed_options ); | |
| $is_in_checked_array = in_array( $checked_option, $allowed_options ); | |
| return ( $is_allowed_options ? $is_in_checked_array : ! $is_in_checked_array ); | |
| } | |
| /** | |
| * Converts path into an array | |
| * | |
| * @param string Path usually in dot notation | |
| * @param string Value of the path property | |
| * @param string Separator string | |
| * @return array Schema property array | |
| */ | |
| function convert_path_to_array( $property, $value, $separator = '.' ) | |
| { | |
| $output = NULL; | |
| foreach( array_reverse( explode( $separator, $property ) ) as $key ) | |
| { | |
| if( empty( $output ) ) | |
| { | |
| $output = array( $key => $value ); | |
| } | |
| else | |
| { | |
| $output = array( $key => $output ); | |
| } | |
| } | |
| return $output; | |
| } | |
| /** | |
| * Get customizer url | |
| * | |
| * @param object Collection | |
| * @return string Customizer URL | |
| */ | |
| function get_customizer_url( $url_Blog = NULL ) | |
| { | |
| global $customizer_relative_url, $Blog; | |
| if( $url_Blog === NULL && isset( $Blog ) ) | |
| { // Use current collection: | |
| $url_Blog = $Blog; | |
| } | |
| return $url_Blog->get_baseurl_root().$customizer_relative_url; | |
| } | |
| /** | |
| * Convert operator alias to jQuery QueryBuilder format | |
| * | |
| * @param string Alias Operator | |
| * @return string Query Builder Operator | |
| */ | |
| function get_querybuilder_operator( $operator ) | |
| { | |
| switch( $operator ) | |
| { | |
| case '=': | |
| return 'equal'; | |
| case '!=': | |
| case '<>': | |
| return 'not_equal'; | |
| case '<': | |
| return 'less'; | |
| case '<=': | |
| return 'less_or_equal'; | |
| case '>': | |
| return 'greater'; | |
| case '>=': | |
| return 'greater_or_equal'; | |
| default: | |
| return $operator; | |
| } | |
| } | |
| /** | |
| * Temporary function to check if we should use defer when loading scripts. | |
| */ | |
| function use_defer() | |
| { | |
| global $disp, $ReqPath, | |
| $use_defer, | |
| $use_defer_for_backoffice, | |
| $use_defer_for_loggedin_users, | |
| $use_defer_for_anonymous_users, | |
| $use_defer_for_anonymous_disp_register, | |
| $use_defer_for_anonymous_disp_register_finish, | |
| $use_defer_for_anonymous_disp_users, | |
| $use_defer_for_anonymous_disp_anonpost, | |
| $use_defer_for_loggedin_disp_single_page, | |
| $use_defer_for_loggedin_disp_front, | |
| $use_defer_for_loggedin_disp_profile, | |
| $use_defer_for_loggedin_disp_pwdchange, | |
| $use_defer_for_loggedin_disp_edit, | |
| $use_defer_for_loggedin_disp_proposechange, | |
| $use_defer_for_loggedin_disp_edit_comment, | |
| $use_defer_for_loggedin_disp_comments, | |
| $use_defer_for_loggedin_disp_visits, | |
| $use_defer_for_loggedin_disp_messages, | |
| $use_defer_for_loggedin_disp_threads, | |
| $use_defer_for_loggedin_disp_users, | |
| $use_defer_for_loggedin_disp_contacts, | |
| $use_defer_for_default_register_form; | |
| $r = $use_defer | |
| && ( is_admin_page() ? $use_defer_for_backoffice : true ) | |
| && ( is_logged_in() ? $use_defer_for_loggedin_users : $use_defer_for_anonymous_users ) | |
| && ( $disp == 'register' ? $use_defer_for_anonymous_disp_register : true ) | |
| && ( $disp == 'register_finish' ? $use_defer_for_anonymous_disp_register_finish : true ) | |
| && ( $disp == 'users' ? $use_defer_for_anonymous_disp_users : true ) | |
| && ( $disp == 'anonpost' ? $use_defer_for_anonymous_disp_anonpost : true ) | |
| && ( empty( $disp ) && $ReqPath == '/htsrv/register.php' ? $use_defer_for_default_register_form : true ) | |
| && ( is_logged_in() && in_array( $disp, array( 'single', 'page' ) ) ? $use_defer_for_loggedin_disp_single_page : true ) | |
| && ( is_logged_in() && $disp == 'front' ? $use_defer_for_loggedin_disp_front : true ) | |
| && ( is_logged_in() && $disp == 'profile' ? $use_defer_for_loggedin_disp_profile : true ) | |
| && ( is_logged_in() && $disp == 'pwdchange' ? $use_defer_for_loggedin_disp_pwdchange : true ) | |
| && ( is_logged_in() && $disp == 'edit' ? $use_defer_for_loggedin_disp_edit : true ) | |
| && ( is_logged_in() && $disp == 'proposechange' ? $use_defer_for_loggedin_disp_proposechange : true ) | |
| && ( is_logged_in() && $disp == 'edit_comment' ? $use_defer_for_loggedin_disp_edit_comment : true ) | |
| && ( is_logged_in() && $disp == 'comments' ? $use_defer_for_loggedin_disp_comments : true ) | |
| && ( is_logged_in() && $disp == 'visits' ? $use_defer_for_loggedin_disp_visits : true ) | |
| && ( is_logged_in() && $disp == 'messages' ? $use_defer_for_loggedin_disp_messages : true ) | |
| && ( is_logged_in() && $disp == 'threads' ? $use_defer_for_loggedin_disp_threads : true ) | |
| && ( is_logged_in() && $disp == 'users' ? $use_defer_for_loggedin_disp_users : true ) | |
| && ( is_logged_in() && $disp == 'contacts' ? $use_defer_for_loggedin_disp_contacts : true ); | |
| return $r; | |
| } | |
| /** | |
| * Get rendering error | |
| * | |
| * @param string Error message | |
| * @param string HTML tag: <p>, <span>, <div> | |
| * @return string | |
| */ | |
| function get_rendering_error( $error_message, $html_tag = 'p' ) | |
| { | |
| if( ! in_array( $html_tag, array( 'p', 'span', 'div' ) ) ) | |
| { // Force not allowed html tag: | |
| $html_tag = 'p'; | |
| } | |
| return '<'.$html_tag.' class="evo_rendering_error">'.$error_message.'</'.$html_tag.'>'; | |
| } | |
| /** | |
| * Display rendering error | |
| * | |
| * @param string Error message | |
| * @param string HTML tag: <p>, <span>, <div> | |
| */ | |
| function display_rendering_error( $error_message, $html_tag = 'p' ) | |
| { | |
| echo get_rendering_error( $error_message, $html_tag ); | |
| } | |
| /** | |
| * Clean up rendering errors `<p class="evo_rendering_error">...</p>` from provided content | |
| * | |
| * @param string Content with rendering error | |
| * @return string Content without rendering error | |
| */ | |
| function clear_rendering_errors( $content ) | |
| { | |
| return preg_replace( '#<([a-z]+) class="evo_rendering_error">.+?</\1>#', '', $content ); | |
| } | |
| ?> |