Source for file _functions.php
Documentation is available at _functions.php
* General purpose functions
* b2evolution - {@link http://b2evolution.net/}
* Released under GNU GPL License - {@link http://b2evolution.net/about/license.html}
* @copyright (c)2003-2005 by Francois PLANQUE - {@link http://fplanque.net/}
* @author This file built upon code from original b2 - http://cafelog.com/
if( !defined('DB_USER') ) die( 'Please, do not access this page directly.' );
require_once( dirname(__FILE__
).
'/_functions_cats.php' );
require_once( dirname(__FILE__
).
'/_functions_blogs.php' );
require_once( dirname(__FILE__
).
'/_functions_bposts.php' );
require_once( dirname(__FILE__
).
'/_functions_users.php' );
require_once( dirname(__FILE__
).
'/_functions_trackback.php' );
require_once( dirname(__FILE__
).
'/_functions_pingback.php' );
require_once( dirname(__FILE__
).
'/_functions_pings.php' );
require_once( dirname(__FILE__
).
'/_functions_skins.php' );
require_once( dirname(__FILE__
).
'/_functions_errors.php' );
require_once( dirname(__FILE__
).
'/_functions_antispam.php' );
if( !isset
( $use_html_checker ) ) $use_html_checker =
1;
if( $use_html_checker ) require_once( dirname(__FILE__
).
'/_class_htmlchecker.php' );
* Report MySQL errors in detail.
* {@internal mysql_oops(-) }}
* @deprecated use class DB instead
* @param string The query which led to the error
* @return boolean success?
$error =
'<p class="error">'.
T_('Oops, MySQL error!').
'</p>'
.
'<p>Your query:<br /><code>'.
$sql_query.
'</code></p>'
.
'<p>MySQL said:<br /><code>'.
mysql_error().
' (error '.
mysql_errno().
')</code></p>';
/***** Formatting functions *****/
* Format a string/content for being output
* {@internal format_to_output(-) }}
* @param string format, can be one of the following
* - 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: strips tags and escapes quotes
* - formvalue: use as a form value: escapes quotes and < > but leaves code alone
* - xml: use in an XML file: strip HTML tags
* - xmlattr: use as an attribute: strips tags and escapes quotes
* @return string formatted text
// display in HTML page body: allow full HTML
// Special mode for RSS 0.92: apply renders and allow full HTML but escape it
// strips out HTML (mainly for use in Title)
// use as an attribute: strips tags and escapes quotes
// use as a form value: escapes quotes and < > but leaves code alone
// use in an XML file: strip HTML tags
// use as an attribute: strips tags and escapes quotes
die( 'Output format ['.
$format.
'] not supported.' );
// echo 'unBR:',htmlspecialchars(str_replace( ' ', '*', $content) );
function format_to_post( $content, $autobr =
0, $is_comment =
0, $encoding =
'' )
global $use_balanceTags, $use_html_checker, $use_security_checker;
global $allowed_tags, $allowed_attribues, $uri_attrs, $allowed_uri_scheme;
global $comments_allowed_tags, $comments_allowed_attribues, $comments_allowed_uri_scheme;
// Replace any & that is not a character or entity reference with &
$content=
preg_replace( '/&(?!#[0-9]+;|#x[0-9a-fA-F]+;|[a-zA-Z_:][a-zA-Z0-9._:-]*;)/', '&', $content );
// may put brs in the middle of multiline tags...
{ // Auto close open tags:
$uri_attrs, $allowed_uri_scheme, $encoding );
$checker =
& new SafeHtmlChecker( $comments_allowed_tags, $comments_allowed_attribues,
$uri_attrs, $comments_allowed_uri_scheme, $encoding );
$checker->check( $content );
if( !isset
( $use_security_checker ) ) $use_security_checker =
1;
if( $use_security_checker )
// Open comments or '<![CDATA[' are dangerous
// i modifier at the end means caseless
if( preg_match ('#\s(on[a-z]+)\s*=#i', $check, $matches)
// action=, background=, cite=, classid=, codebase=, data=, href=, longdesc=, profile=, src=
||
preg_match ('#=["\'\s]*(javascript|vbscript|about):#i', $check, $matches)
||
preg_match ('#\<\/?\s*(frame|iframe|applet|object)#i', $check, $matches) )
if( $is_comment &&
preg_match ('#\s(style|class|id)\s*=#i', $check, $matches) )
$content =
preg_replace("/(\015\012)|(\015)|(\012)/", "<br />\n", $content);
$content =
preg_replace("/<br>\n/", "\n", $content); //for PHP versions before 4.0.5
function zeroise($number, $threshold)
{ // function to add leading zeros when necessary
for ($i =
0; $i <
($threshold -
$l); $i =
$i +
1) { $number=
'0'.
$number; }
* Convert all non ASCII chars (except if UTF-8) to &#nnnn; unicode references.
* Also convert entities to &#nnnn; unicode references if output is not HTML (eg XML)
* Preserves < > and quotes.
* {@internal convert_chars(-)}}
* sakichan: pregs instead of loop
global $b2_htmltrans, $b2_htmltranswinuni;
// Convert highbyte non ASCII/UTF-8 chars to urefs:
{ // This is a single byte charset
// Convert Windows CP1252 => Unicode (valid HTML)
// TODO: should this go to input conversions instead (?)
$content =
strtr( $content, $b2_htmltranswinuni );
// Convert & chars that are not used in an entity
$content =
preg_replace('/&(?![#A-Za-z0-9]{2,20};)/', '&', $content);
// 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);
* Make links clickable in a given text.
* {@internal NOTE: its tested in the misc.funcs.simpletest.php test case }}
* @todo IMHO it would be better to use "\b" (word boundary) to match the beginning of links..
* original function: phpBB, extended here for AIM & ICQ
* fplanque restricted :// to http:// and mailto://
array( '#(^|[\s>])(https?|mailto)://([^<>{}\s]+[^.,<>{}\s])#i',
'#(^|[\s>])aim:([^,<\s]+)#i',
'#(^|[\s>])www\.([a-z0-9\-]+)\.([a-z0-9\-.\~]+)((?:/[^<\s]*)?[^.,\s])#i',
'#(^|[\s>])([a-z0-9\-_.]+?)@([^,<\s]+)#i', ),
array( '$1<a href="$2://$3">$2://$3</a>',
'$1<a href="aim:goim?screenname=$2$3'.
$moredelim.
'message='.
rawurlencode(T_('Hello')).
'">$2$3</a>',
'$1<a href="http://wwp.icq.com/scripts/search.dll?to=$2">$2</a>',
'$1<a href="http://www.$2.$3$4">www.$2.$3$4</a>',
'$1<a href="mailto:$2@$3">$2@$3</a>', ),
/***** // Formatting functions *****/
* with enhanced format string
function mysql2date( $dateformatstring, $mysqlstring, $useGM =
false )
if( empty($m) ) return false;
$unixtimestamp =
mktime(substr($m,11,2),substr($m,14,2),substr($m,17,2),substr($m,5,2),substr($m,8,2),substr($m,0,4));
return date_i18n( $dateformatstring, $unixtimestamp, $useGM );
* date internationalization: same as date() formatting but with i18n support
function date_i18n( $dateformatstring, $unixtimestamp, $useGM =
false )
global $month, $month_abbrev, $weekday, $weekday_abbrev, $weekday_letter;
$datemonth =
date('m', $unixtimestamp);
$dateweekday =
date('w', $unixtimestamp);
if( $dateformatstring ==
'isoZ' )
{ // full ISO 8601 format
$dateformatstring =
'Y-m-d\TH:i:s\Z';
{ // We want a Greenwich Meridian time:
$j =
gmdate($dateformatstring, $unixtimestamp -
($Settings->get('time_difference') *
3600));
{ // We want default timezone time:
$dateformatstring =
' '.
$dateformatstring; // will be removed later
// echo $dateformatstring, '<br />';
$dateformatstring =
preg_replace("/([^\\\])l/", '\\1@@@\\l@@@', $dateformatstring);
$dateformatstring =
preg_replace("/([^\\\])D/", '\\1@@@\\D@@@', $dateformatstring);
$dateformatstring =
preg_replace("/([^\\\])e/", '\\1@@@e@@@', $dateformatstring);
$dateformatstring =
preg_replace("/([^\\\])F/", '\\1@@@\\F@@@', $dateformatstring);
$dateformatstring =
preg_replace("/([^\\\])M/", '\\1@@@\\M@@@', $dateformatstring);
$dateformatstring =
substr($dateformatstring, 1, strlen($dateformatstring)-
1);
// echo $dateformatstring, '<br />';
$j =
date($dateformatstring, $unixtimestamp);
$j =
str_replace( '@@@D@@@', T_($weekday_abbrev[$dateweekday]), $j);
$j =
str_replace( '@@@e@@@', T_($weekday_letter[$dateweekday]), $j);
* Get start and end day of a week, based on week number and start-of-week
* {@internal get_weekstartend(-)}}
$my =
substr($mysqlstring, 0, 4);
$mm =
substr($mysqlstring, 5, 2);
$md =
substr($mysqlstring, 8, 2);
$day =
mktime(0, 0, 0, $mm, $md, $my);
$weekday =
date('w', $day);
while( $weekday >
$start_of_week )
$weekday =
date('w', $day);
$week['start'] =
$day +
86400 -
$i;
$week['end'] =
$day +
691199;
for ($i =
0; $i <
strlen($emailaddy); $i =
$i +
1) {
$emailNOSPAMaddy .=
'&#' .
ord( substr( $emailaddy, $i, 1 ) ).
';';
$emailNOSPAMaddy .=
substr($emailaddy, $i, 1);
$emailNOSPAMaddy =
str_replace('@', '@', $emailNOSPAMaddy);
* Check that email address looks valid
#$chars = "/^([a-z0-9_]|\\-|\\.)+@(([a-z0-9_]|\\-)+\\.)+[a-z]{2,4}\$/i";
$chars =
'/^.+@[^\.].*\.[a-z]{2,}$/i';
{ // displays a warning box with an error message (original by KYank)
<script language="JavaScript">
<!-- this is for non-JS browsers (actually we should never reach that code, but hey, just in case...) -->
<?php echo
$msg; ?><br />
<a href="
<?php echo
$_SERVER["HTTP_REFERER"]; ?>">
<?php echo
T_('go back') ?></a>
{ // asks a question - if the user clicks Cancel then it brings them back one page
<script language="JavaScript">
if (!confirm("
<?php echo
$msg ?>")) {
<script language="JavaScript">
window.location = "
<?php echo
$url; ?>";
setTimeout("redirect();", 100);
<p>
<?php echo
T_('Redirecting you to:') ?> <strong>
<?php echo
$title; ?></strong><br />
<?php printf( T_('If nothing happens, click <a %s>here</a>.'), ' href="'.
$url.
'"' ); ?></p>
// functions to count the page generation time (from phpBB2)
// ( or just any time between timer_start() and timer_stop() )
$mtime =
$mtime[1] +
$mtime[0];
function timer_stop($display=
0,$precision=
3) { //if called like timer_stop(1), will echo $timetotal
global $timestart,$timeend;
$mtime =
$mtime[1] +
$mtime[0];
$timetotal =
$timeend-
$timestart;
global $post_default_title;
if (preg_match('/<title>(.+?)<\/title>/is', $content, $matchtitle))
$post_title =
$matchtitle[1];
$post_title =
$post_default_title;
* Also used by post by mail
if (preg_match('/<category>([0-9]+?)<\/category>/is', $content, $matchcat))
* xmlrpc_removepostdata(-)
$content =
preg_replace('/<title>(.*?)<\/title>/si', '', $content);
$content =
preg_replace('/<category>(.*?)<\/category>/si', '', $content);
$content =
trim($content);
* xmlrpc_displayresult(-)
if( $display ) echo
T_('No response!'),"<br />\n";
elseif( $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());
// We'll display the response:
$value_arr .=
' ['.
$blah.
'] ';
if( $display ) echo
T_('Response'), ': ', $value_arr, "<br />\n";
if( $display ) echo
T_('Response'), ': ', $value ,"<br />\n";
if ($debug ==
1 &&
( !empty($filename) ) )
$fp =
fopen($filename, $mode);
Balances Tags of string using a modified stack.
@param text Text to be balanced
@return Returns balanced text
@author Leonard Lin (leonard@acm.org)
1.2 ***TODO*** Make better - change loop condition to $text
1.1 Fixed handling of append/stack pop order of end text
# b2 bug fix for comments - in case you REALLY meant to type '< !--'
# b2 bug fix for LOVE <3 (and other situations with '<' before a number)
while( preg_match("/<(\/?\w*)\s*([^>]*)>/", $text, $regex) )
$newtext =
$newtext .
$tagqueue;
if( substr($regex[1],0,1) ==
'/' )
// if too many closing tags
//or close to be safe $tag = '/' . $tag;
// if stacktop value = tag close value then pop
else if ($tagstack[$stacksize -
1] ==
$tag) { // found closing tag
$tag =
'</' .
$tag .
'>'; // Close Tag
} else { // closing tag not at top, search for it
for ($j=
$stacksize-
1;$j>=
0;$j--
) {
if ($tagstack[$j] ==
$tag) {
for ($k=
$stacksize-
1;$k>=
$j;$k--
){
$tagqueue .=
'</' .
array_pop ($tagstack) .
'>';
// Push if not img or br or hr
if($tag !=
'br' &&
$tag !=
'img' &&
$tag !=
'hr') {
// $attributes = $regex[2];
$attributes =
' '.
$attributes;
$tag =
'<'.
$tag.
$attributes.
'>';
$newtext .=
substr($text,0,$i) .
$tag;
$newtext =
$newtext .
$tagqueue;
$newtext =
$newtext .
'</' .
$x .
'>'; // Add remaining tags to close
# b2 fix for the bug with HTML comments
$newtext =
str_replace( '< !--', '<'.
'!--', $newtext ); // the concatenation is needed to work around some strange parse error in PHP 4.3.1
* Clean up the mess PHP has created with its funky quoting everything!
* {@internal remove_magic_quotes(-)}}
* @param mixed string or array (function is recursive)
{ // That stupid PHP behaviour consisting of adding slashes everywhere is unfortunately on
foreach($mixed as $k =>
$v)
// echo 'Removing slashes ';
* Sets a parameter with values from the request or to provided default,
* except if param is already set!
* Also removes magic quotes if they are set automatically by PHP.
* Priority order: POST, GET, COOKIE, DEFAULT.
* @param string Variable to set
* @param string Force value type to one of:
* @param mixed Default value or TRUE if user input required
* @param boolean Do we need to memorize this to regenerate the URL for this page?
* @param boolean Override if variable already set
* @param boolean Force setting of variable to default?
* @return mixed Final value of Variable, or false if we don't force setting and and did not set
* @todo add option to override what's already set. DONE.
function param( $var, $type =
'', $default =
'', $memorize =
false, $override =
false, $forceset =
true )
global $global_param_list;
// WARNING: when PHP register globals is ON, COOKIES get priority over GET and POST with this!!!
if( !isset
( $
$var ) ||
$override )
if( isset
($_POST[$var]) )
// echo "$var=".$$var." set by POST!<br/>";
elseif( isset
($_GET["$var"]) )
// echo "$var=".$$var." set by GET!<br/>";
elseif( isset
($_COOKIE[$var]))
// echo "$var=".$$var." set by COOKIE!<br/>";
elseif( $default ===
true )
die( '<p class="error">'.
sprintf( T_('Parameter %s is required!'), $var ).
'</p>' );
// echo "$var=".$$var." set to default<br/>";
{ // param not found! don't set the variable.
// Won't be memorized nor type-forced!
{ // Variable was already set but we need to remove the auto quotes
// echo $var, ' already set';
/* if($var == 'post_extracats' )
{ echo "$var=".$$var." was already set! count = ", count($$var),"<br/>";
// type will be forced even if it was set before and not overriden
// echo $var, '=', $$var, '<br>';
{ // Memorize this parameter
if( !isset
($global_param_list) )
{ // Init list if necessary:
$global_param_list =
array();
// echo "Memorize(".count($global_param_list).") 'var' => $var, 'type' => $type, 'default' => $default <br>";
$global_param_list[$var] =
array( 'type' =>
$type, 'default' =>
$default );
// echo $var, '(', gettype($$var), ')=', $$var, '<br />';
* Regenerate current URL from parameters
* But it is also useful when generating static pages: you cannot rely on $_REQUEST[]
global $global_param_list, $ReqPath;
$ignore =
array( $ignore );
if( isset
($global_param_list) ) foreach( $global_param_list as $var =>
$thisparam )
$type =
$thisparam['type'];
$defval =
$thisparam['default'];
{ // we don't want to include that one
if( (! empty($catsel)) &&
(strpos( $cat, '-' ) ===
false) )
{ // It's worthwhile retransmitting the catsels
foreach( $catsel as $value )
if( ! empty($show_status) )
foreach( $show_status as $value )
// echo "var=$var, type=$type, defval=[$defval], val=[$value] \n";
if( (!empty($value)) &&
($value !=
$defval) )
{ // Value exists and is not set to default value:
// echo "adding $var \n";
// else echo "ignoring $var \n";
$url =
empty($pagefileurl) ?
$ReqPath :
$pagefileurl;
global $core_subdir, $skins_subdir, $basepath;
return $basepath.
'/'.
$skins_subdir;
if( strpos( $string, "'" ) !==
0 )
{ // no quote at position 0
$string =
"'".
$string.
"'";
* Check the validity of a given URL
* Checks allowed URI schemes and URL ban list
* @param string Url to validate
* @param array Allowed URI schemes (see /conf/_formatting.php)
* @return mixed false or error message
{ // Empty URL, no problem
([a-z][a-z0-9+.\-]*):[0-9]* # scheme
// # authority absolute URLs only
[a-z][a-z0-9~+.\-_,:;/\\\\]* # Don t allow anything too funky like entities
([?#][a-z0-9~+.\-_,:;/\\\\%&=#]*)?
{ // Cannot vaidate URL structure
return T_('Invalid URL');
if(!in_array( $scheme, $allowed_uri_scheme ))
return T_('URI scheme not allowed');
// Search for blocked URLs:
if( $debug ) return 'Url refused. Debug info: blacklisted word: ['.
$block.
']';
return T_('URL not allowed');
* wrap pre around var_dump(), better debuggin'
* {@internal pre_dump(-) }}
* @param mixed variable to dump
* @param string title to display
$debug_messages =
array();
* Log a debug string to be displayed later. (Typically at the end of the page)
* {@internal debug_log(-) }}
* @param boolean true to force output
$debug_messages[] =
$message;
* Outputs debug info. (Typically at the end of the page)
* {@internal debug_info(-) }}
* @param boolean true to force output
echo
'<div class="debug"><h2>Debug info</h2>';
{ // don't display changing time when we want to test obhandler
$time_page =
$Timer->get_duration( 'main' );
$time_queries =
$Timer->get_duration( 'sql_queries' );
$percent_queries =
$time_page >
0 ?
number_format( 100/
$time_page *
$time_queries, 2 ) :
0;
echo
'Page processing time: '.
$time_page.
' seconds.<br/>';
echo
'SQL processing time: '.
$time_queries.
' seconds, '.
$percent_queries.
'%.<br/>';
if( count( $debug_messages ) )
echo
'<h3>Debug messages</h3><ul>';
foreach( $debug_messages as $message )
echo
'Old style queries: ', $querycount, '<br />';
echo
'DB queries: ', $DB->num_queries, '<br />';
* It will be set in /blogs/evocore/_main.php and handle the output.
* It strips every line and generates a md5-ETag, which is checked against the one eventually
* being sent by the browser.
* {@internal obhandler(-) }}
* @param string output given by PHP
global $lastmodified, $use_gzipcompression, $use_etags;
// we're sending out by default
if( !isset
( $lastmodified ) )
{ // default of lastmodified is now
// prefix with PUB or AUT.
$ETag =
'"AUT'; // A private page
else $ETag =
'"PUB'; // and public one
$ETag .=
md5( $out ).
'"';
// decide to send out or not
if( isset
($_SERVER['HTTP_IF_NONE_MATCH'])
header( 'Content-Length: 0' );
header( $_SERVER['SERVER_PROTOCOL'] .
' 304 Not Modified' );
#log_hit(); // log this somehow?
// Send Last-Modified -----------------
// We should perhaps make this the central point for this.
// Also handle Cache-Control and Pragma here (with global vars).
// header( 'Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', $lastmodified) );
&& isset
($_SERVER['HTTP_ACCEPT_ENCODING'])
&&
strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') )
header( 'Content-Encoding: gzip' );
* Add param(s) at the end of an URL, using either ? or & depending on exiting url
* @param string existing url
* @param string params to add
* @param string delimiter to use for more params
if( strpos( $url, '?' ) !==
false )
{ // There are already params in the URL
return $url.
$moredelim.
$param;
// These are the first params
* Add a tail (starting with /) at the end of an URL before any params (starting with ?)
* @param string existing url
* @param string tail to add
return $parts[0].
$tail.
'?'.
$parts[1];
* sends a mail, wraps PHP's mail() function.
* $current_locale will be used to set the charset
* @param string recipient
* @param string subject of the mail
* @param string the message
* @param string From address, being added to headers
* @param array additional headers
function send_mail( $to, $subject, $message, $from =
'', $headers =
array() )
global $b2_version, $current_locale, $locales;
{ // make sure $headers is an array
$headers =
array( $headers );
// Specify charset and content-type of email
$headers[] =
'Content-Type: text/plain; charset='.
$locales[ $current_locale ]['charset'];
$headers[] =
'X-Mailer: b2evolution '.
$b2_version.
' - PHP/'.
phpversion();
{ // from has to go into headers
$headerstring =
"From: $from\n";
{ // add supplied headers
$headerstring .=
implode( "\n", $headers );
debug_log( "Sending mail from $from to $to - subject $subject." );
return @mail( $to, $subject, $message, $headerstring );