/foo/ $context['path_to_root'] =& preg_replace(array('|/([^/]*)/\.\./|', '|/\./|'), '/', $context['path_to_root']); // the safe library include_once $context['path_to_root'].'shared/safe.php'; // the logging facility include_once $context['path_to_root'].'agents/logger.php'; // load general parameters (see control/configure.php) Safe::load('shared/parameters.include.php'); // load users parameters (see users/configure.php) Safe::load('users/parameters.include.php'); // see all errors on development machine if(isset($context['with_debug']) && ($context['with_debug'] == 'Y')) $level = E_ALL; // mask notifications and warnings else $level = E_ALL ^ (E_NOTICE | E_USER_NOTICE | E_WARNING); // set reporting level Safe::error_reporting($level); // default value for name filtering in forms (e.g. 'edit_name' filled by anonymous surfers) if(!defined('FORBIDDEN_CHARS_IN_NAMES')) define('FORBIDDEN_CHARS_IN_NAMES', '/[^\s\w-:&#;\'"]+/'); // default value for url filtering in forms if(!defined('FORBIDDEN_CHARS_IN_URLS')) define('FORBIDDEN_CHARS_IN_URLS', '/[^\w~_:@\/\.&#;\,+%\?=-]+/'); // default value for allowed tags if(!isset($context['users_allowed_tags'])) $context['users_allowed_tags'] = '
    • '; // default P3P compact policy enables IE support of our cookies, even through frameset -- http://support.microsoft.com/kb/323752 if(!isset($context['p3p_compact_policy'])) $context['p3p_compact_policy'] = 'CAO PSA OUR'; // the name of this server if(isset($_SERVER['HTTP_HOST'])) $context['host_name'] = strip_tags($_SERVER['HTTP_HOST']); // from HTTP request elseif(isset($_SERVER['SERVER_NAME'])) $context['host_name'] =& strip_tags($_SERVER['SERVER_NAME']); // from web daemon configuration file else $context['host_name'] = 'localhost'; // strip port number, if any if($here = strrpos($context['host_name'], ':')) $context['host_name'] =& substr($context['host_name'], 0, $here); // the url to the front page -- to be used alone, or with an appended string starting with '/' if(isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == 443)) $context['url_to_home'] = 'https://'.$context['host_name']; elseif(isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] != 80)) $context['url_to_home'] = 'http://'.$context['host_name'].':'.$_SERVER['SERVER_PORT']; else $context['url_to_home'] = 'http://'.$context['host_name']; // the url to be used for self-referencing in forms -- starting at the server root if(isset($_SERVER['SCRIPT_NAME']) && preg_match('/\.php$/', $_SERVER['SCRIPT_NAME'])) $context['self_script'] = $_SERVER['SCRIPT_NAME']; elseif(isset($_SERVER['SCRIPT_URL']) && preg_match('/\.php$/', $_SERVER['SCRIPT_URL'])) // not SCRIPT_URI !!! $context['self_script'] = $_SERVER['SCRIPT_URL']; elseif(isset($_SERVER['REQUEST_URI']) && preg_match('/\.php$/', $_SERVER['REQUEST_URI'])) $context['self_script'] = $_SERVER['REQUEST_URI']; elseif(isset($_SERVER['PHP_SELF']) && preg_match('/\.php$/', $_SERVER['PHP_SELF'])) // for PHP as CGI ??? $context['self_script'] = $_SERVER['PHP_SELF']; // launched from the command line if(!isset($_SERVER['REMOTE_ADDR'])) $context['self_url'] = ''; // the url to reference ourself, including query string elseif(isset($_SERVER['REQUEST_URI'])) $context['self_url'] = $context['url_to_home'].$_SERVER['REQUEST_URI']; elseif(isset($_SERVER['QUERY_STRING'])) $context['self_url'] = $context['url_to_home'].$context['self_script'].'?'.$_SERVER['QUERY_STRING']; else $context['self_url'] = $context['url_to_home'].$context['self_script']; // where yacs has been installed, relative to the root of the web server if(!isset($context['url_to_root'])) { $context['url_to_root'] = '/'; if(isset($context['self_url'])) { $items = @parse_url($context['self_url']); if(preg_match('/(.*?\/yacs.*?\/)/i', $items['path'], $matches)) $context['url_to_root'] = $matches[1]; elseif(preg_match('/(\/.*?\/)control/i', $items['path'], $matches)) $context['url_to_root'] = $matches[1]; } } // save parameter to be used in control/configure.php $context['url_to_root_parameter'] = $context['url_to_root']; // compute gmt offset based on system configuration $context['gmt_offset'] = intval((strtotime(date('M d Y H:i:s')) - strtotime(gmdate('M d Y H:i:s'))) / 3600); // analyze script args (e.g. 'articles/view.php/123/3', where '123' is the article id, and '3' is the page number) // and remember url position, which may differ from path position because of extra args. // // strip query string from request URI if(isset($_SERVER['REQUEST_URI'])) { $path = str_replace('?'.$_SERVER['QUERY_STRING'], '', $_SERVER['REQUEST_URI']); if($here = strpos($path, '.php/')) { // keep only the tail of the request $path = substr($path, $here+strlen('.php/')); // split all args, if any $context['arguments'] = array(); $arguments = explode('/', $path); if(is_array($arguments)) { foreach($arguments as $argument) $context['arguments'][] = rawurldecode($argument); } } } // // Transcoding stuff -- after setting of skin variant // /** * remove quoting recursively * * This function extends [code]stripslashes()[/code] to arrays. * Should probably be part of the next version of native PHP library? * * @param array of encoded fields * @return the transformed array */ function stripslashes_recursively($fields) { if(!is_array($fields)) return $fields; foreach($fields as $name => $value) { if(is_array($value)) $fields[$name] = stripslashes_recursively($value); else $fields[$name] = stripslashes($value); } return $fields; } // The very first thing is to manage parameters transmitted to scripts. // If get_magic_quotes_gpc is on, we strip all slashes if(@count($_REQUEST) && get_magic_quotes_gpc()) $_REQUEST = stripslashes_recursively($_REQUEST); if(@count($_COOKIE) && get_magic_quotes_gpc()) $_COOKIE = stripslashes_recursively($_COOKIE); // support utf-8 include_once $context['path_to_root'].'shared/utf8.php'; // user agent specifies which character set to use if(isset($_SERVER['HTTP_ACCEPT_CHARSET'])) { // utf-8 is ok if(preg_match('/\butf-8\b/i', $_SERVER['HTTP_ACCEPT_CHARSET'])) $context['charset'] = 'utf-8'; // back to another charset else $context['charset'] = 'iso-8859-15'; // use also utf-8 where there is no specification } else $context['charset'] = 'utf-8'; // transcode bookmarklet input -- see sections/view.php if(isset($_REQUEST['text']) && $_REQUEST['text']) { // transcode what we have in iso-8859-1 to utf-8 if(is_callable('mb_convert_variables')) $charset = mb_convert_variables('UTF-8', 'ISO-8859-1', $_REQUEST); } /** * encode a form field * * This function encode special HTML chars, such as '&', '<', etc. * It also preserves Unicode entities, meaning that if you have typed an euro sign in an article, * you will still have an euro sign in the edit form instead of a mysterious €. * * Use it to encode every textarea or input field in forms. * Use it also to generate XML data, such as RSS feed. * * @param string some text to be encoded * @return the string to be displayed */ function encode_field($text) { global $context; // transcode HTML entities to unicode include_once $context['path_to_root'].'shared/utf8.php'; $text =& utf8::transcode($text); // encode special chars $text = htmlspecialchars($text); // preserve unicode entities $text = preg_replace(array('/&#/i', '/&u/i'), array('&#', '&u'), $text); // special encoding if($context['charset'] == 'iso-8859-15') $text = utf8::to_iso8859($text); // escape double quotes as well $text = str_replace('"', '"', $text); // done return $text; } // ok, this is a hack to convert utf-8 encoding to unicode entities // should be deleted when we will support utf-8 end-to-end... if(($context['charset'] == 'utf-8') && is_array($_REQUEST) && count($_REQUEST)) $_REQUEST =& utf8::decode_recursively($_REQUEST); // // Localization and internationalization // // store strings localized internally -- obsoleted by gettext global $local; // To localize strings internally you should put alternative strings in the $local array, and use the // i18n::user() function to select the correct version. // // Example: // $local['mystring_en'] = 'my string'; // $local['mystring_fr'] = 'ma chaine de caracteres'; // $text = i18n::user('mystring'); // // the internationalization library include_once $context['path_to_root'].'i18n/i18n.php'; // initialize the library i18n::initialize(); // load strings localized externally for shared scripts i18n::bind('shared'); // localize messages generated in the background i18n::bind('agents'); // // Switch management // // redirect if the server has been switched off and if not in the control panel, nor in the script directory if(file_exists($context['path_to_root'].'switch.off') && !preg_match('/\/(control|scripts)\//i', $context['self_script'])) Safe::redirect($context['url_to_home'].$context['url_to_root'].'control/closed.php'); // if no parameters file, jump to the control panel, if not in it already if(!is_readable($context['path_to_root'].'shared/parameters.include.php') && !preg_match('/(\/control\/|setup.php)/i', $context['self_script'])) Safe::redirect($context['url_to_home'].$context['url_to_root'].'control/index.php'); // // Access the database // // the SQL virtualization library include_once $context['path_to_root'].'shared/sql.php'; // initialize connections to the database if(!SQL::initialize()) exit(i18n::s('Unable to activate the database server.')); // // Skind and rendering -- see skins/index.php for more information // // load the layout interface include_once $context['path_to_root'].'skins/layout.php'; // load skins parameters, if any Safe::load('skins/parameters.include.php'); // skin variant is asked for explicitly if(isset($_REQUEST['variant']) && $_REQUEST['variant']) $context['skin_variant'] = $_REQUEST['variant']; // force the skin variant if polled by AvantGo if(!isset($context['skin_variant']) && is_callable('getenv') && base64_decode(getenv("HTTP_X_AVANTGO_DEVICEOS"))) { $context['skin_variant'] = 'mobile'; Logger::debug('polled by AvantGo'); } /** * load a skin * * This function will declare the Skin class related to the skin declared in [code]$context['skin'][/code]. * It does it by loading a file named [code]'skin.php'[/code]. * For example, if you have stated: [code]$context['skin'] = 'skins/myskin'[/code], * then [code]load_skin()[/code] will include [code]'skins/myskin/skin.php'[/code]. * * The skin can be overriden by changing options of a section, or of an article. * In both cases, use the keyword 'skin_foo' to have YACS look for the skin [code]foo[/code] and related files. * * The variant parameter determines the template used for page rendering. * If 'foo' is provided, YACS will look for template [code]template_foo.php[/code] in the target skin. * * The variant can be overriden by changing options of a section, or of an article. * In both cases, use the keyword 'variant_foo' to have YACS look for template [code]template_foo.php[/code] in the target skin. * * @param string a skin variant, if any * @param object anchor of the target item, if any * @param string additional options for the target item, if any */ function load_skin($variant='', $anchor=NULL, $options='') { global $context; // ensure we have a site name if(!isset($context['site_name'])) $context['site_name'] = $context['host_name']; // use a specific skin, if any if($options && preg_match('/\bskin_(.+?)\b/i', $options, $matches)) $context['skin'] = 'skins/'.$matches[1]; elseif(is_object($anchor) && ($skin = $anchor->has_option('skin')) && is_string($skin)) $context['skin'] = 'skins/'.$skin; // ensure we have a skin name elseif(!isset($context['skin'])) $context['skin'] = 'skins/default'; // load skin basic library include_once $context['path_to_root'].'skins/skin_skeleton.php'; // load actual skin if(is_readable($context['path_to_root'].$context['skin'].'/skin.php')) { include_once $context['path_to_root'].$context['skin'].'/skin.php'; // no skin found, create a fake one } else { // the dummy skin class class Skin extends Skin_Skeleton { function build_block($text, $variant='') { if($variant == 'page_title') return '

      '.$text."

      \n"; else return '
      '.$text."
      \n"; } function build_box($title, $text, $variant='') { if($title) return '

      '.$title."

      \n".'
      '.$text."
      \n"; return '
      '.$text."
      \n"; } function build_link($url, $text=NULL, $variant=NULL, $href_title=NULL) { global $context; if($url && ($text != '_')) return ''.$text." "; else return $text." "; } function build_list($items, $variant='', $default_icon=NULL) { if(!is_array($items)) return $items.BR; $text = ''; foreach($items as $url => $label) { $prefix = $suffix = ''; if(is_array($label)) { $prefix = $label[0]; $suffix = $label[2]; $label = $label[1]; } $text .= '
    • '.$prefix.Skin::build_link($url, $label, 'basic').$suffix.'
    • '; } return "
        \n".$text."
      \n"; } function initialize() { if(!defined('BR')) define('BR', '
      '); if(!defined('EOT')) define('EOT', '/>'); } } } // the codes library include_once $context['path_to_root'].'codes/codes.php'; // skin variant is already set -- maybe already set as 'mobile' if(isset($context['skin_variant'])) ; // use item variant elseif($options && preg_match('/\bvariant_(.+?)\b/i', $options, $matches)) $context['skin_variant'] = $matches[1]; // use anchor variant elseif(is_object($anchor) && ($anchor_variant = $anchor->has_option('variant')) && is_string($anchor_variant)) $context['skin_variant'] = $anchor_variant; // use provided variant else $context['skin_variant'] = $variant; // // initialize template variables (alphabetical order) -- see skins/test.php // // where developers can add debugging messages --one string per row if(!isset($context['debug']) || !is_array($context['debug'])) $context['debug'] = array(); // the place to display error messages if(!isset($context['error'])) $context['error'] = NULL; // content of the extra panel if(!isset($context['extra'])) $context['extra'] = ''; // content of the navigation panel if(!isset($context['navigation'])) $context['navigation'] = ''; // page author (meta-information) if(!isset($context['page_author'])) $context['page_author'] = ''; // additional content for the page footer --cache restriction if(!isset($context['page_footer'])) $context['page_footer'] = ''; // additional meta-information to be put in page header if(!isset($context['page_header'])) $context['page_header'] = ''; // the main image in the page if(!isset($context['page_image'])) $context['page_image'] = ''; // the main bar of menus if(!isset($context['page_menu'])) $context['page_menu'] = array(); // page publisher (meta-information) if(!isset($context['page_publisher'])) $context['page_publisher'] = ''; // page main title if(!isset($context['page_title'])) $context['page_title'] = ''; // page breadcrumbs if(!isset($context['path_bar'])) $context['path_bar'] = array(); // prefix for page main content if(!isset($context['prefix'])) $context['prefix'] = ''; // suffix for page main content if(!isset($context['suffix'])) $context['suffix'] = ''; // page main content if(!isset($context['text'])) $context['text'] = ''; // initialize skin constants if(!defined('BR')) Skin::load(); } /** * actually render a page * * This function loads the template declared in [code]$context['skin'][/code]. * * It does it by loading a file named [code]'template.php'[/code]. * For example, if you have stated: [code]$context['skin'] = 'skins/myskin/'[/code], * then [code]render_skin()[/code] will include [code]'skins/myskin/template.php'[/code]. * * Moreover, if a template is available for the current module, it will be loaded instead. * For example, if the module is [code]'sections'[/code], and if there a file named [code]'template_sections.php'[/code], * this one will be loaded instead of the standard [code]'template.php'[/code]. * * The assumption here is that the executing script is either at the root level (e.g., the main * [code]index.php[/code] of the server) or at some level below (e.g., [code]articles/index.php[/code]). * Moreover, we are also assuming that the skin has been designed for the root level only. * Therefore, it has to be adapted for other scripts. * For example, [code][/code] has to be * changed to [code][/code]. Of course, we would like to avoid * doing such a change on-the-fly to stay efficient. * * To cope with all these requirements, this function builds and uses a cached version of the * modified skin, according to the following algorithm: * - select the template file (e.g., [code]'skins/myskin/template.php'[/code]) * - if the file exists, use it (we are at the root level) * - else if the cache file exists, use it (we are at one level down or below) * - else build and use the cache file (e.g., [code]'skins/myskin/template_cache.php'[/code]) * * This function will also highlight some words in the page if we are coming from a search engine. * [*] Words to be highlighted can be found in [code]$_SERVER['HTTP_REFERER'][/code] if we are coming from Google * (field 'q'), from Yahoo (field 'p') or from the YACS search page (field 'search'). * Alternatively, this script will also consider [code]$_REQUEST['highlight'][/code]. * [*] This function look for highlighted words in following page components: * [code]$context['text'][/code] and [code]$context['title'][/code]. * The style class [code]'highlight'[/code] is used. * [*] This function is activated only at [code]view.php[/code] scripts. * * @link http://www.webreference.com/programming/php/cookbook/chap11/1/6.html preg_match to highlight code * * This function will attempt to handle web caching through following mechanisms: * * [*] If [code]$context['text'][/code] has some content, and if there is no [code]send_body()[/code] function, * we assume that the page may be versioned. * In this case a [code]ETag headers[/code] is computed as being the hash of page content. * Page content is defined as resulting from the concatenation of: [code]$context['page_title'][/code], * [code]$context['text'][/code] and [code]$context['extra'][/code]. * Also, modification dates of [code]shared/parameters.include.php[/code] and of [code]skins/parameters.include.php[/code], to reflect * any change of one of the main configuration files. * Lastly, the surfer capability is appended as well, to cope with login/logout correctly. * If the content changes, the value of the [code]ETag[/code] will change accordingly, and the new page will be provided. * Else the transaction will break on code [code]304 Not Modified[/code], savings some bytes and some time. * This mechanism is very efficient for the front page and for some index pages, * for which the content may vary but dates are difficult to evaluate. * * [*] If a time stamp is provided, the script sets the [code]Last-Modified[/code] attribute in responses and manages * the [code]If-Modified-Since[/code] attribute in requests. * If two dates are the same (and if [code]ETag[/code] attribute is not provided), the transaction will break on * code [code]304 Not Modified[/code], savings some bytes and some time. * Else the new page will be transmitted to the requestor. * This mechanism is very efficient for article pages, for which an edition date can be precisely assessed. * * This script does not set or modify other attributes in responses. * These attributes may be set directly in scripts according to following suggestions: * * [*] Internet Explorer may have strange behaviour with the [code]Expire[/code] attribute. * It does not take into account very short-term expiration date and does not validate after the deadline. * On the other hand, setting an expiration date is useful to fix the 'download a .zip file directly from the browser' bug. * We recommend to set this attribute in all scripts related to file transfers and download, and to not set it * at all in every other script. * * [*] The [code]Cache-Control[/code] attribute allows for cache-control. * It has been primarily designed for HTTP/1.1 agents, and few proxies seem to handle it correctly at the moment. * However to explicitly declare that the output of some script may be cached for three hours by intermediate proxies, * you can use [code]Safe::header("Cache-Control: public, max-age=10800");[/code]. * On the other hand, if only the user-agent (i.e., the browser) is allowed to cache something, * you can use [code]Safe::header("Cache-Control: private, max-age=10800");[/code]. * * [*] What to do with [code]Pragma:[/code]? Well, almost nothing; this is used only by some legacy browsers. * If you want an old browser to cache some object, use [code]Safe::header("Pragma:");[/code]. * * Post-processing hooks are triggered after all HTML is returned to the browser, * including the poor-man's cron so the user who kicks off the cron jobs should not notice any delay. * * @param string the cache id to be used for the global page content * @param int the time() to be used as the Last-Modified date of the page, if any */ function render_skin($script_cache_id=NULL, $stamp=NULL) { global $context, $local; // put here ALL global variables to be included in template, including $local // allow for only one call -- see scripts/validate.php global $rendering_fuse; if(isset($rendering_fuse)) return; $rendering_fuse = TRUE; // words may be highlighted, but only while viewing information if(preg_match('/view\.php/', $context['self_script'])) { // highlight words if we are coming from a search engine $highlight = ''; if(isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER']) { // coming from this server if(preg_match('/\b'.preg_quote($context['host_name'], '/').'\/.*\?(.*)/', $_SERVER['HTTP_REFERER'], $matches)) { $items = explode('&', $matches[1]); while($item = each($items)) { list($name, $value) = explode('=', $item['value']); if($name == 'search') { $highlight = urldecode($value); break; } } // coming from all the web } elseif(preg_match('/\balltheweb\b.*\?(.*)/', $_SERVER['HTTP_REFERER'], $matches)) { $items = explode('&', $matches[1]); while($item = each($items)) { list($name, $value) = explode('=', $item['value']); if($name == 'q') { $highlight = urldecode($value); break; } } // coming from ask } elseif(preg_match('/\bask\b.*\?(.*)/', $_SERVER['HTTP_REFERER'], $matches)) { $items = explode('&', $matches[1]); while($item = each($items)) { list($name, $value) = explode('=', $item['value']); if($name == 'q') { $highlight = urldecode($value); break; } } // coming from feedster } elseif(preg_match('/\bfeedster\b.*\?(.*)/', $_SERVER['HTTP_REFERER'], $matches)) { $items = explode('&', $matches[1]); while($item = each($items)) { list($name, $value) = explode('=', $item['value']); if($name == 'q') { $highlight = urldecode($value); break; } } // coming from google } elseif(preg_match('/\bgoogle\b.*\?(.*)/', $_SERVER['HTTP_REFERER'], $matches)) { $items = explode('&', $matches[1]); while($item = each($items)) { list($name, $value) = explode('=', $item['value']); if($name == 'q') { $highlight = urldecode($value); break; } } // coming from yahoo } elseif(preg_match('/http:\/\/.*\.yahoo\..*\/.*\?(.*)/', $_SERVER['HTTP_REFERER'], $matches)) { $items = explode('&', $matches[1]); while($item = each($items)) { list($name, $value) = explode('=', $item['value']); if($name == 'p') { $highlight = urldecode($value); break; } } } } // explicitly hightlight some words, if required if(isset($_REQUEST['highlight']) && $_REQUEST['highlight']) $highlight = $_REQUEST['highlight']; // make unicode HTML entities $highlight = utf8::to_unicode($highlight); // minimum size for any search token - depends of mySQL setup $query = "SHOW VARIABLES LIKE 'ft_min_word_len'"; if(($row =& SQL::query_first($query)) && ($row['Value'] > 0)) define('MINIMUM_TOKEN_SIZE', $row['Value']); // by default MySQL indexes words with at least four chars if(!defined('MINIMUM_TOKEN_SIZE')) define('MINIMUM_TOKEN_SIZE', 4); // kill short and redundant tokens $tokens = preg_split('/[\s,]+/', $highlight); if(@count($tokens)) { $highlight = ''; foreach($tokens as $token) { // too short if(strlen(preg_replace('/&.+?;/', 'x', $token)) < MINIMUM_TOKEN_SIZE) continue; // already here (repeated word) if(strpos($highlight, $token) !== FALSE) continue; // keep this token $highlight .= $token.' '; } $highlight = trim($highlight); } // make an array if($highlight) $highlight = explode(' ', $highlight); // actual highlighting, if any if(is_array($highlight) && @count($highlight)) { // build search and replace patterns $words = array(); $highlighted_words = array(); $extra = array(); for ($i = 0, $j = count($highlight); $i < $j; $i++) { $words[$i] = '/('.preg_quote($highlight[$i], '/').')/i'; // up to three highlighting colors $highlighted_words[$i] = '$1'; $extra[] = ''.$highlight[$i].''; } // highlight in title $input = $context['page_title']; $output = ''; while($input) { if(preg_match('/^([^<]*)?(<.*?>)?(.*)$/s', $input, $matches)) { $output .= preg_replace($words, $highlighted_words, $matches[1]); $output .= $matches[2]; $input = $matches[3]; } } $context['page_title'] = $output; // highlight in main text $input = $context['text']; $output = ''; while($input) { if(preg_match('/^([^<]*)?(<.*?>)?(.*)$/s', $input, $matches)) { $output .= preg_replace($words, $highlighted_words, $matches[1]); $output .= $matches[2]; $input = $matches[3]; } } $context['text'] = $output; // list highlighted words in the extra panel $context['extra'] .= Skin::build_box(i18n::s('Highlighted'), Skin::build_list($extra, 'compact'), 'navigation', 'highlighted').$context['extra']; } } // yes, we are proud of this piece of software Safe::header('X-Powered-By: YACS (http://www.yetanothercommunitysystem.com/)'); // provide P3P compact policy, if any if(isset($context['p3p_compact_policy'])) Safe::header('P3P: CP="'.$context['p3p_compact_policy'].'"'); // inform proxies that we may serve several versions per reference Safe::header('Vary: Accept-Encoding, Cookie, ETag, If-None-Match, Set-Cookie'); // handle web cache if(!(isset($context['without_http_cache']) && ($context['without_http_cache'] == 'Y')) && !headers_sent()) { // ask for revalidation in any case - 'no-cache' is mandatory for IE6 !!! Safe::header('Expires: Thu, 19 Nov 1981 08:52:00 GMT'); Safe::header('Cache-Control: private, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0'); Safe::header('Pragma:'); // validate the content if hash is ok - content depends also on configuration files and on surfer capability if($context['text'] && !is_callable('send_body')) { // concatenate significant content $content = ''; if(isset($context['debug']) && count($context['debug'])) $content .= implode(':', $context['debug']).':'; if(isset($context['error'])) $content .= $context['error'].':'; if(isset($context['extra'])) $content .= $context['extra'].':'; if(isset($context['navigation'])) $content .= $context['navigation'].':'; if(isset($context['page_image'])) $content .= $context['page_image'].':'; if(isset($context['page_title'])) $content .= $context['page_title'].':'; if(isset($context['prefix'])) $content .= $context['prefix'].':'; if(isset($context['suffix'])) $content .= $context['suffix'].':'; if(isset($context['text'])) $content .= $context['text'].':'; if(isset($context['etag'])) $content .= $context['etag'].':'; $content .= Safe::filemtime($context['path_to_root'].'shared/parameters.include.php').':' .Safe::filemtime($context['path_to_root'].'skins/parameters.include.php').':' .Surfer::get_capability(); // hash content to create the etag string $etag = '"'.md5($content).'"'; // always sent, even in case of 304, according to RFC2616 Safe::header('ETag: '.$etag); if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && is_array($if_none_match = explode(',', str_replace('\"', '"', $_SERVER['HTTP_IF_NONE_MATCH'])))) { foreach($if_none_match as $target) { if(trim($target) == $etag) { Safe::header("HTTP/1.0 304 Not Modified"); return; } } } } // validate the content if stamp is ok if($stamp > 1000000) { $last_modified = gmdate('D, d M Y H:i:s', $stamp).' GMT'; Safe::header('Last-Modified: '.$last_modified); if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && ($if_modified_since = preg_replace('/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']))) { if(($if_modified_since == $last_modified) && !isset($_SERVER['HTTP_IF_NONE_MATCH'])) { Safe::header("HTTP/1.0 304 Not Modified"); return; } } } } // if it was a HEAD request, stop here if(isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'HEAD')) return; // normalize customized components of the head if(isset($context['site_head'])) $context['page_header'] .= $context['site_head']."\n"; // the title $page_title = ucfirst(strip_tags(preg_replace('/\[(.*?)\]/s', '', $context['page_title']))); $context['page_header'] .= ''.$page_title; if($context['site_name'] && !preg_match('/'.str_replace('/', ' ', strip_tags($context['site_name'])).'/', strip_tags($context['page_title']))) { if($page_title) $context['page_header'] .= ' - '; $context['page_header'] .= strip_tags($context['site_name']); } $context['page_header'] .= "\n"; // set icons for this site if(isset($context['site_icon']) && $context['site_icon']) { $context['page_header'] .= ''."\n"; // other components of the head $context['page_header'] .= ''."\n".''."\n"; // insert one tabulation before each header line $context['page_header'] = "\t".str_replace("\n", "\n\t", $context['page_header'])."\n"; // append dynamic javascript libraries $context['page_footer'] .= ''."\n"; // handle the output correctly Safe::ob_start('yacs_handler'); Safe::header('Content-Type: text/html; charset='.$context['charset']); // load the page library require_once $context['path_to_root'].'skins/page.php'; // load a template for this module if(isset($context['skin_variant']) && is_readable($context['path_to_root'].$context['skin'].'/template_'.$context['skin_variant'].'.php')) require_once $context['path_to_root'].$context['skin'].'/template_'.$context['skin_variant'].'.php'; // else use the original template elseif(is_readable($context['path_to_root'].$context['skin'].'/template.php')) require_once $context['path_to_root'].$context['skin'].'/template.php'; // no valid template has been loaded, build one from scratch else { global $context; echo "\n\n"; // the title echo ''.ucfirst(strip_tags($context['page_title'])); if($context['site_name'] && isset($context['skin_variant']) && ($context['skin_variant'] != 'home')) echo ' - '.$context['site_name']; echo ''; echo "\n\n"; // display the title if($context['page_title']) echo '

      '.$context['page_title']."

      \n"; // display the page menu if(is_array($context['page_menu']) && count($context['page_menu'])) echo Skin::build_list($context['page_menu'], 'page_menu'); // display the error message, if any if($context['error']) echo $context['error']."\n"; // render and display the content, if any echo $context['text']; $context['text'] = ''; // display the dynamic content, if any if(is_callable('send_body')) send_body(); // maybe some additional text has been created in send_body() echo $context['text']; // debug output, if any if(is_array($context['debug']) && count($context['debug'])) echo "\n".'
        '."\n".'
      • '.implode('
      • '."\n".'
      • ', $context['debug']).'
      • '."\n".'
      '."\n"; // render and display extra content, if any if($context['extra']) echo '\n"; } // something has been buffered if($script_cache_id && is_callable('ob_get_length') && ob_get_length()) { // cache full page content --we are aiming to save on spikes if(is_callable(array('cache', 'put')) && is_callable('ob_get_contents') && ($text = ob_get_contents())) Cache::put($script_cache_id, $text, 'global'); // ensure everything has been sent to the browser if(is_callable('ob_end_flush')) while(@ob_end_flush()); } // tick finalize_page(); } /** * render raw content * * This function only installs the output handler. * * @param string content type of the output * */ function render_raw($type) { global $context; // allow for only one call -- see scripts/validate.php global $rendering_fuse; if(isset($rendering_fuse)) return; $rendering_fuse = TRUE; // sanity check if(!$type) $type = 'text/html; charset='.$context['charset']; // handle the output correctly Safe::ob_start('yacs_handler'); if(!headers_sent()) Safe::header('Content-Type: '.$type); } /** * finalize page rendering * * @see shared/cache.php * * @param boolean TRUE to not return, FALSE to allow for subsequent execution steps */ function finalize_page($no_return=FALSE) { global $context; // HEAD -- see scripts/validate.php if(isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'HEAD')) $no_return = FALSE; // no tick on error if(isset($context['skin_variant']) && ($context['skin_variant'] == 'error')) if($no_return) exit; else return; // allow for only one call -- see scripts/validate.php global $finalizing_fuse; if(isset($finalizing_fuse)) if($no_return) exit; else return; $finalizing_fuse = TRUE; // don't stop even if the browser connection dies Safe::ignore_user_abort(TRUE); // if this is more than a HEAD request if(isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] != 'HEAD')) { // stamp user last presence include_once $context['path_to_root'].'users/users.php'; if(is_callable(array('Users', 'click'))) Users::click(); } // the post-processing hooks Safe::load('shared/hooks.include.php'); if(is_callable(array('Hooks', 'include_scripts'))) { // statistics, etc... Hooks::include_scripts('finalize'); // the poor man's cron, except if actual cron has been activated if(isset($context['with_cron']) && ($context['with_cron'] == 'Y')) if($no_return) exit; else return; // tick only on selected regular scripts if(!preg_match('/\b(view|index)\.php\b/', $context['self_script'])) if($no_return) exit; else return; // boost the front page -- no tick here if(isset($context['skin_variant']) && ($context['skin_variant'] == 'home')) if($no_return) exit; else return; // get date of last tick include_once $context['path_to_root'].'shared/values.php'; $stamp = Values::get('cron.tick', NULL_DATE); // wait at least 5 minutes = 300 seconds between ticks if($stamp) $target = strtotime($stamp) + 300; else $target = time(); if($target > time()) if($no_return) exit; else return; // remember cron tick if(isset($context['with_debug']) && ($context['with_debug'] == 'Y')) Logger::remember('cron.php', 'tick'); // trigger background processing Hooks::include_scripts('tick'); // remember tick date Values::set('cron.tick', gmstrftime('%Y-%m-%d %H:%M:%S').' GMT'); } // no other rendering, if applicable if($no_return) exit; else return; } /** * general purpose handler * * This handler is able: * - to transcode data to another character set * - to compress data, if asked for it * - to set the Content-Length HTTP header * * We have it because the standard 'ob_gzhandler' does not set the Content-Length header properly. * Also, this function transcodes char to valid Unicode if necessary. * * @link http://dev.e-taller.net/gzhandler/miscGzHandler.phps * * @parameter string the buffered page * @return string the string to actually transmit */ function yacs_handler($content) { global $context; // restore Unicode entities, if any if($context['charset'] == 'utf-8') $content = utf8::from_unicode($content); // empty string cannot be compressed if(empty($content)) $compress = FALSE; // compression has been disabled elseif(isset($context['with_compression']) && ($context['with_compression'] != 'Y')) $compress = FALSE; // this run-time cannot compress elseif(!is_callable('gzcompress')) $compress = FALSE; // user agent does not support compression elseif(!isset($_SERVER['HTTP_ACCEPT_ENCODING'])) $compress = FALSE; // user agent does not support gzip compression elseif(!preg_match('/(x-gzip|gzip)\b/i', $_SERVER['HTTP_ACCEPT_ENCODING'], $matches)) $compress = FALSE; // headers have already been sent elseif(headers_sent()) $compress = FALSE; // YES, you can compress else $compress = TRUE; // send plain data if(!$compress) { if(!headers_sent()) Safe::header('Content-Length: '.strlen($content)); return $content; } // compress data $data = gzcompress($content, 5); $data = pack('cccccccc',0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00) .substr($data, 0, -4) .pack('V',crc32($content)) .pack('V',strlen($content)); Safe::header('Content-Encoding: '.$matches[1]); Safe::header('Content-Length: '.strlen($data)); return $data; } /** * check HTML/XHTML syntax * * This function uses some PHP XML parser to validate the provided string. * The objective is to spot malformed or unordered HTML and XHTML tags. No more, no less. * * @param string the string to check * @return string the error message, if any * * @see actions/edit.php * @see articles/edit.php * @see comments/edit.php * @see locations/edit.php * @see sections/edit.php * @see servers/edit.php * @see tables/edit.php * @see users/edit.php */ function validate($input) { global $context; // assume syntax is ok $text = ''; // sanity check if(!is_callable('create_function')) return ''; // do not validate code nor snippet $input = preg_replace(array('/(.+?)<\/code>/ise', '/
      (.+?)<\/pre>/ise', '/\[escape\](.+?)\[\/escape\]/ise', '/\[php\](.+?)\[\/php\]/ise', '/\[snippet\](.+?)\[\/snippet\]/ise'), "'
      '.str_replace('<', '<', '$1').'
      '", $input); // make a supposedly valid xml snippet $snippet = ''."\n".''."\n".preg_replace(array('/&(?!(amp|#\d+);)/i', '/< /i', '/ >/i'), array('&', '< ', ' >'), $input)."\n".''."\n"; // remember tags during parsing global $validation_stack; $validation_stack = array(); // create a parser $xml_parser = xml_parser_create(); $startElement = create_function( '$parser, $name, $attrs', 'global $validation_stack; array_push($validation_stack, $name);' ); $endElement = create_function( '$parser, $name', 'global $validation_stack; array_pop($validation_stack);' ); xml_set_element_handler($xml_parser, $startElement, $endElement); // spot errors, if any if(!xml_parse($xml_parser, $snippet, TRUE)) { $text .= '

      '.sprintf(i18n::s('XML error: %s at line %d'), xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser)-2).BR."\n"; $lines = explode("\n", $snippet); $text .= htmlentities($lines[xml_get_current_line_number($xml_parser)-1]).BR."\n"; $element = array_pop($validation_stack); if(!preg_match('/snippet/i', $element)) $text .= sprintf(i18n::s('Last stacking element: %s'), $element); $text .= '

      '."\n"; } // clear resources xml_parser_free($xml_parser); // return parsing result return $text; } // // Anchor stuff -- see shared/anchor.php for more information // /** * load one anchor from the database * * You should expand this function to take into account any new module * that may behave as anchors for other information components. * * This function saves on SQL requests by caching results locally in static memory. * * You can prevent YACS to cache mutable objects by setting the second parameter to TRUE. * * @param string a valid anchor (e.g., 'section:12', 'article:34') * @param boolean TRUE to always fetch a fresh instance, FALSE to enable cache * @return an array of named variables, or NULL if the anchor is unknown * * @see shared/anchor.php */ function &get_anchor($id, $mutable=FALSE) { global $context; // cache previous answers static $anchors; if(!is_array($anchors)) $anchors = array(); // cache hit, but only for immutable objects if(!$mutable && isset($anchors[$id])) return $anchors[$id]; // noanchor yet $anchor = NULL; // find the type $attributes = explode(':', $id); if(!isset($attributes[1]) || !$attributes[1]) return $anchor; // switch on type switch($attributes[0]) { case 'article': include_once $context['path_to_root'].'articles/article.php'; $anchor =& new Article(); $anchor->load_by_id($attributes[1]); break;; case 'category': include_once $context['path_to_root'].'categories/category.php'; $anchor =& new Category(); $anchor->load_by_id($attributes[1]); break; case 'file': include_once $context['path_to_root'].'files/file.php'; $anchor =& new File(); $anchor->load_by_id($attributes[1]); break;; case 'section': include_once $context['path_to_root'].'sections/section.php'; $anchor =& new Section(); $anchor->load_by_id($attributes[1]); break; case 'user': include_once $context['path_to_root'].'users/user.php'; $anchor =& new User(); $anchor->load_by_id($attributes[1]); break; default: return $anchor; } // save in cache $anchors[$id] = $anchor; // here it is return $anchor; } /** * delete items related to one anchor * * @param the anchor (e.g., 'article:12') * @return an error message, if any */ function delete_related_to($anchor) { global $context; // clear the cache as well include_once $context['path_to_root'].'shared/cache.php'; // delete related articles Cache::clear('articles'); include_once $context['path_to_root'].'articles/articles.php'; if($text = Articles::delete_for_anchor($anchor)) return $text; // delete related comments Cache::clear('comments'); include_once $context['path_to_root'].'comments/comments.php'; if($text = Comments::delete_for_anchor($anchor)) return $text; // delete related dates Cache::clear('dates'); include_once $context['path_to_root'].'dates/dates.php'; if($text = Dates::delete_for_anchor($anchor)) return $text; // // delete related decisions // Cache::clear('decisions'); // include_once $context['path_to_root'].'decisions/decisions.php'; // if($text = Decisions::delete_for_anchor($anchor)) // return $text; // delete related files Cache::clear('files'); include_once $context['path_to_root'].'files/files.php'; if($text = Files::delete_for_anchor($anchor)) return $text; // delete related images Cache::clear('images'); include_once $context['path_to_root'].'images/images.php'; if($text = Images::delete_for_anchor($anchor)) return $text; // delete related links Cache::clear('links'); include_once $context['path_to_root'].'links/links.php'; if($text = Links::delete_for_anchor($anchor)) return $text; // delete related locations Cache::clear('locations'); include_once $context['path_to_root'].'locations/locations.php'; if($text = Locations::delete_for_anchor($anchor)) return $text; // delete related tables Cache::clear('tables'); include_once $context['path_to_root'].'tables/tables.php'; if($text = Tables::delete_for_anchor($anchor)) return $text; // delete related pointers Cache::clear(array('members', 'categories')); include_once $context['path_to_root'].'categories/members.php'; if($text = Members::unlink_for_anchor($anchor)) return $text; // delete related trials if(is_readable($context['path_to_root'].'overlays/classic_trials/classic_trials.php')) { Cache::clear('classic_trials'); include_once $context['path_to_root'].'overlays/classic_trials/classic_trials.php'; if($text = Classic_Trials::delete_for_anchor($anchor)) return $text; } // everything has gone fine return NULL; } /** * get the label for an action * * Following actions codes have been defined: * - 'article:create' * - 'article:update' * - 'article:publish' * - 'article:review' * - 'section:create' * - 'section:update' * - 'comment:create' * - 'comment:update' * - 'file:create' * - 'file:update' * - 'link:create' * - 'link:update' * - 'link:stamp' * - 'image:create' * - 'image:update' * - 'image:set_as_icon' * - 'location:create' * - 'location:update' * - 'table:create' * - 'table:update' * - 'user:create' * - 'user:update' * * @param string the action code example: 'article:publish', 'image:create' * @return a string * @see shared/anchor.php#touch */ function get_action_label($action) { if(preg_match('/.*:import/i', $action)) return i18n::s('imported'); switch($action) { case 'article:create': return i18n::s('created'); case 'article:update': return i18n::s('edited'); case 'article:publish': return i18n::s('published'); case 'article:review': return i18n::s('reviewed'); case 'section:create': return i18n::s('created'); case 'section:update': return i18n::s('updated'); case 'comment:create': return i18n::s('commented'); case 'comment:update': return i18n::s('edited'); case 'file:create': return i18n::s('file uploaded'); case 'file:update': return i18n::s('file updated'); case 'image:create': return i18n::s('image uploaded'); case 'image:update': return i18n::s('image updated'); case 'image:set_as_icon': return i18n::s('icon set'); case 'link:create': case 'link:feed': return i18n::s('link posted'); case 'link:update': return i18n::s('link updated'); case 'link:stamp': return i18n::s('page updated'); case 'location:create': return i18n::s('location created'); case 'location:update': return i18n::s('location updated'); case 'table:create': return i18n::s('table posted'); case 'table:update': return i18n::s('table updated'); case 'thread:update': return i18n::s('new message'); case 'user:create': return i18n::s('new user'); case 'user:update': return i18n::s('profile updated'); default: return i18n::s('edited'); } } ?>