* Jerome Lavigne * * $Id$ ******************************************************************************/ define('LP_CONTEXT_INDEX', 1); define('LP_CONTEXT_CATEGORY', 2); define('LP_CONTEXT_ARCHIVES', 4); define('LP_CONTEXT_POST', 8); define('LP_CONTEXT_STATIC', 16); define('LP_CONTEXT_SEARCH', 32); define('LP_CONTEXT_AUTHOR', 64); define('LP_CONTEXT_TAG', 128); define('LP_CONTEXT_ALL', LP_CONTEXT_INDEX | LP_CONTEXT_CATEGORY | LP_CONTEXT_ARCHIVES | LP_CONTEXT_POST | LP_CONTEXT_STATIC | LP_CONTEXT_SEARCH | LP_CONTEXT_AUTHOR | LP_CONTEXT_TAG); // maybe move the following to a (class) method, when (if) we add user-defined contexts define('LP_CONTEXTS', 'index category archives post static search author tag'); class Frontend { var $_hooks = array( 'get_posts', 'get_posts_loop', 'get_comments', 'insert_comment', 'start_render', 'sidebar', 'parse_post', 'parse_comment', 'render_comments', 'pre_render', 'post_render', 'shutdown'); // set in constructor var $options; var $raw_urls = false; var $db; var $cache; var $tpl; var $messages; var $tables_prefix; var $tables; var $title; var $where_date; var $templates = array( 'index' => 'index.xml', 'header' => 'header.xml', 'sidebar' => 'sidebar.xml', 'footer' => 'footer.xml'); var $_plugins = array(); var $_plugin_dir = array(); // getCategories var $_categories; // getCatGroups var $_catgroups; // getArchives var $_archives; // getContext var $context; // checkCategory var $_category_nicenames; // getPosts var $_posts; var $num_posts; var $_post_date; // cleared in getPosts, set in renderPost var $posts_join; var $posts_where; var $posts_in; // getPages var $_pages; // getPostTags var $_posttags; // getTags var $_tags; // startRender var $_templates_set; // renderPost var $current_post; // parsing helpers var $_block_post; var $_sidebar; // getMailHelper var $_mail_helper; // getSpamChecker var $_spam_checker; // userFromWpCookie var $_user_from_cookie; // extractKeywords; var $search_keywords; // base messages, merged with the ones from the localized file var $_base_messages = array( 'no_posts' => 'No post found', 'comments_open_nocomments' => 'Be the first to add a comment to this article.', 'comments_closed_nocomments' => 'Comments closed.', 'comments_closed' => 'Comments closed.', 'categories_header' => 'Categories', 'comment_accepted' => 'Comment posted.', 'comment_required_fields' => 'Please fill in all required fields', 'comment_form_message' => "Your email address is required but won't be displayed.", 'comment_mail_subject' => 'New comment', 'post_mail_subject' => 'Reading suggestion', 'mail_sent' => 'Message sent.', 'post_continued' => 'continued...', 'search_title' => 'Search Results', 'comment' => 'comment', 'comments' => 'comments', 'post' => 'post', 'posts' => 'posts', 'thereis' => 'There is', 'thereare' => 'There are' ); function Frontend(&$db_options, &$options, $cache=false, $view_type='html') { $this->cache =& $cache; $this->view_type = $view_type; /********************************************************************** * table names **********************************************************************/ if (!isset($db_options['prefix'])) $this->raiseError('missing tables prefix in config.php', 'Frontend', __LINE__, E_USER_ERROR); if (empty($db_options['prefix'])) { $prefix = $this->tables_prefix = ''; } else { $prefix = $this->tables_prefix = $db_options['prefix']; $prefix .= '_'; } $_tables = array( 'options', 'categories', 'posts', 'post2cat', 'comments', 'users', 'usermeta', 'postmeta', 'links', 'linkcategories', 'stats', 'tags','link2cat'); $this->tables = array(); foreach ($_tables as $k) $this->tables[$k] = "${prefix}$k"; /********************************************************************** * options **********************************************************************/ if (!isset($options) || !is_array($options) || (count($options) == 0)) { // connection is delayed if you use fs-based options to speed up fs-based caches // TODO: check if this is really needed with latest DBlite versions $this->_connect($db_options); // retrieve from db $this->db->query("select replace(option_name,'lp_opt_','') as option_name, option_value from " . $this->tables['options'] . " where option_name like 'lp_opt_%' or option_name in ('gmt_offset', 'wordpress_api_key','moderation_keys', 'blacklist_keys', 'comment_max_links')"); foreach ($this->db->all() as $row) { $options[$row['option_name']] = $row['option_value']; } if (count($options) == 0) $this->raiseError('lightpress options not set in database', 'Frontend', __LINE__, E_USER_ERROR); // sanitize if (isset($options['posts_per_page'])) $options['posts_per_page'] = (int)$options['posts_per_page']; if (isset($options['posts_per_rss'])) $options['posts_per_rss'] = (int)$options['posts_per_rss']; if (isset($options['approx_sql_tstamp'])) $options['approx_sql_tstamp'] = (int)$options['approx_sql_tstamp']; if (isset($options['plugins']) && is_string($options['plugins']) && !empty($options['plugins'])) $options['plugins'] = unserialize($options['plugins']); if (isset($options['gmt_offset'])) { // ugly but using a separate query or checking every option name would be a waste $options['gmt_offset'] = (int)$options['gmt_offset'] * 3600; } if (!isset($options['wp_version'])) $options['wp_version'] = '2.0.2'; foreach (array('copyright_notice', 'description') as $o) { if (isset($options[$o])) $options[$o] = stripslashes($options[$o]); } // add plugins if none are set if (!isset($options['plugins']) || empty($options['plugins'])) { $options['plugins'] = $this->getDefaultPlugins(); } } $options['wpff_revision'] = substr(substr('$Revision$', 11), 0, -2); $options['wpff_version'] = $this->getVersion(); $options = array_merge($this->getBaseOptions(), $options); $this->options =& $options; // set basedir (we cannot set it in config.php anymore since it may have been defined in the db) if (DIRECTORY_SEPARATOR == '/') ini_set('include_path', ini_get('include_path') . ':' . $options['basedir']); else ini_set('include_path', ini_get('include_path') . ';' . $options['basedir']); foreach($this->getCriticalOptions() as $option_name) { if (!isset($options[$option_name])) die("Cannot start LightPress Frontend: missing option '$option_name' in configuration file."); } // set base options for those that could not be easily assigned in _base_options if (!isset($options['wp_url'])) $options['wp_url'] = $options['url']; // default installation is to replace WP // guess URL for templates - templates dir should either be in site root or in the WP's lightpress plugin dir // TODO: make this work for non-standard configs (or should users just ignore this var in such a case?) if (strstr(strtolower($options['basedir']), 'wp-content')) { $options['template_url'] = $options['wp_url'] . '/wp-content/plugins/lightpress/themes'; $options['js_url'] = $options['wp_url'] . '/wp-content/plugins/lightpress/js'; } else { $options['template_url'] = $options['url'] . '/themes'; $options['js_url'] = $options['url'] . '/js'; } require_once 'classes/ascii.php'; /********************************************************************** * permalink stuff **********************************************************************/ if (empty($options['post_prefix'])) { $this->raw_urls = true; } else { if (!isset($options['post_permalink_struct'])) $options['post_permalink_struct'] = $this->prefixToPermalinkStruct($options['post_prefix']); if (!isset($options['archives_permalink_struct'])) $options['archives_permalink_struct'] = $this->prefixToPermalinkStruct($options['archives_prefix']); $options['post_permalink_category_name'] = strpos($options['post_permalink_struct'], '%%4$s') !== false; } /********************************************************************** * db instance **********************************************************************/ $this->_connect($db_options); /********************************************************************** * set needed attributes **********************************************************************/ $now = time(); if ($options['approx_sql_tstamp'] > 0) $now = ceil($now / $options['approx_sql_tstamp']) * $options['approx_sql_tstamp']; $this->where_date = gmdate("Y-m-d H:i:s", $now); $this->title = $options['blogname']; /********************************************************************** * localized messages **********************************************************************/ $lang_tokens = explode('_', $options['lang']); $lang = $lang_tokens[0]; $config_messages_file = implode( DIRECTORY_SEPARATOR, array($options['basedir'], 'lang', $lang . '_' . $options['charset'] . '.php')); if (is_readable($config_messages_file)) { require_once $config_messages_file; $config_messages = array_merge($this->_base_messages, $config_messages); $this->messages =& $config_messages; } else { // en_US UTF-8 defaults $this->messages =& $this->_base_messages; } /********************************************************************** * set context **********************************************************************/ $this->context =& $this->getContext(); /********************************************************************** * locale **********************************************************************/ // TODO: trap wrong locale name setlocale(LC_TIME, $options['lang'] . '.' . $options['charset']); /********************************************************************** * template instance **********************************************************************/ require_once 'classes/Template.php'; $this->tpl =& new Template(sprintf( '%s/themes/%s/%s.%s', $options['basedir'], $options['template'], $lang, $options['charset'])); // set options as template variables $tpl_options = array(); foreach ($options as $k=>$v) { if (is_array($v)) continue; $tpl_options["option_$k"] = $v; } if (isset($options['plugin_names']) && is_array($options['plugin_names'])) { foreach ($options['plugin_names'] as $p) $tpl_options[$p] = ''; } $tpl_options['lp_context_type'] = $this->context['type']; $tpl_options['context_url'] = $this->context['url']; $tpl_options['page_self'] = $_SERVER['REQUEST_URI']; $this->tpl->setVar($tpl_options); unset($tpl_options); /********************************************************************** * sort out plugins **********************************************************************/ $this->_plugins =& $this->getPlugins($options['plugins'], $this->context['type']); } function _connect($db_options) { if (!is_null($this->db)) return; require_once 'classes/DBlite/MySQL.php'; $this->db =& new DBlite_MySQL($db_options); } function &getPlugins($plugins_map, $current_context, $hook=null) { $plugins = array(); if (is_null($hook)) { $hooks =& $this->_hooks; } else { $hooks = array($hook); } foreach ($hooks as $hook) $plugins[$hook] = array(); $local_context = 'LP_CONTEXT_' . strtoupper($this->context['type']); $local_context = defined($local_context) ? constant($local_context) : LP_CONTEXT_INDEX; $this->local_context = $local_context; $hide_plugins = array(); foreach ($plugins_map as $plugin_name=>$plugin_details) { list($context_type, $plugin_args) = $plugin_details; $hideplugin = true; if ($context_type & $local_context) { if (@include_once("plugins/${plugin_name}.php")) { $plugin =& new $plugin_name($this, $plugin_args); if ($plugin->active) { foreach (array_intersect($hooks, $plugin->hooks) as $hook) { $plugins[$hook][] =& $plugin; } $this->_plugin_dir[strtolower(get_class($plugin))] =& $plugin; $hideplugin = false; } } else { // TODO: deal with missing plugins, log to a file? } } if ($hideplugin) $hide_plugins['PLUGIN_' . strtoupper($plugin_name)] = ''; } $this->tpl->setVar($hide_plugins); return $plugins; } function &getPlugin($plugin_name) { $plugin_name = strtolower($plugin_name); if (isset($this->_plugin_dir[$plugin_name])) return $this->_plugin_dir[$plugin_name]; $ret = ''; return $ret; } function _processPlugins($hook, &$hook_data, $hide=false) { // render plugins $plugins =& $this->_plugins; if (isset($plugins[$hook])) { foreach ($plugins[$hook] as $k => $v) { if ($hide) $plugins[$hook][$k]->hide(); else $plugins[$hook][$k]->run($hook, $hook_data); } } } /*************************************************************************** * get data methods **************************************************************************/ function &getCategories($force=false) { /* * gets blog categories and builds the structures needed to parse * the menu, and posts * * if you call this method manually before the parse methods, the * structures are cached and reused later */ if (is_null($this->_categories) || $force) { $tables =& $this->tables; $q = " select cat_id, category_parent, cat_name, category_nicename, category_description, category_count as numposts from {$tables['categories']} order by cat_name "; $this->db->query($q); $categories = array(); $catgroups = array(); foreach ($this->db->all() as $category) { if ($this->raw_urls) $category['category_permalink'] = 'index.php?c=' . $category['cat_id']; else $category['category_permalink'] = $this->options['category_prefix'] . '/' . $category['category_nicename']; $categories[$category['cat_id']] = $category; } $this->_categories =& $categories; } return $this->_categories; } function &getArchives($force=false) { /* * gets blog archives and builds the structure used later to parse * the sidebar * * if you call this method manually before the parse methods, the * structures are cached and reused later */ if (is_null($this->_archives) || $force) { if (!$force && !is_null($this->cache) && $this->cache->active) { // try to fetch from the cache $archives = $this->cache->getCache('_frontend_get_archives', '', false); if (!empty($archives)) { $archives = @unserialize($archives); if (is_array($archives)) { $this->_archives =& $archives; return $archives; } } } $tables =& $this->tables; $q = "select year(post_date) as y, lpad(month(post_date), 2, '0') as m, count(*) as numposts from " . $tables['posts'] . " as p where {$this->get_posts_clause} and {$this->only_published} group by y, m order by y desc, m desc"; $result = $this->db->query($q); $archives = array(); $archive_label =& $this->options['archive_label']; $archive_prefix =& $this->options['archives_prefix']; // PHP tends to forget the locale setlocale(LC_TIME, $this->options['lang'] . '.' . $this->options['charset']); foreach ($this->db->all() as $m) { $month = (int)$m['m']; $m['month'] = $month; $archive_date = mktime(0, 0, 0, $month, 1, (int)$m['y']); $m['date'] = $archive_date; $m['label'] = strftime($archive_label, $archive_date); if ($this->raw_urls) $m['permalink'] = 'index.php?m=' . $m['y'] . $m['m']; else $m['permalink'] = strftime($this->options['archives_permalink_struct'], $archive_date); $date = $m['y'] . sprintf("%02d", $month); $archives[$date] = $m; } $this->_archives =& $archives; if (!is_null($this->cache) && $this->cache->active) { $_archives = serialize($archives); $this->cache->setCache($_archives, '_frontend_get_archives', '', false); } } return $this->_archives; } function &getContext($context_type=null, $context_filter=null, $context_index=0, $context_reqtype='') { /* * context type is post/category/archives/static/search/author, etc. * context filter is the key used to retrieve the main object (eg post name) * context index is the starting index when displaying lists of objects * context reqtype is used to distinguish all-purpose plugins from specialized (eg html/feed plugins) * */ $tables =& $this->tables; $options =& $this->options; $context = array( 'type' => $context_type, 'filter' => $context_filter, 'hierarchy' => array(), 'index' => $context_index, 'url' => '', 'label' => '', 'reqtype' => $context_reqtype, 'queryvars' => array('f'=>'', 'j'=>" RIGHT JOIN {$tables['users']} u on p.post_author=u.id ", 'w'=>'', 'g'=>'', 'o' => ' ORDER BY p.post_date DESC ' )); // where clause for published posts in WP 2.1 $this->only_published = " (p.post_status = 'publish') "; /*************************************************************************** * context type and filter **************************************************************************/ // shortcut context for the WP 2.0 'preview' feature if (isset($_GET['preview']) && $_GET['preview'] == 'true' && isset($_GET['p'])) { $user = $this->userFromWpCookie(); if (!is_null($user)) { $preview_id = strip_tags($_GET['p']); if ($user['user_level'] > 4 || isset($user['capabilities']['administrator']) || isset($user['capabilities']['editor'])) { $context['type'] = 'preview'; $context['filter'] = $preview_id; } else if ($this->db->query("select 1 from {$this->tables_prefix}_posts where ID = $preview_id and post_author = {$user['id']}")) { $context['type'] = 'preview'; $context['filter'] = $preview_id; } if ($context['type'] == 'preview') { // fix the date used in where clauses to display future posts if (preg_match('/2.1/',$options['wp_version'])) { $this->only_published = ''; } else { $this->where_date = gmdate("Y-m-d H:i:s", time() + (365 * 24 * 60 * 60)); } } } } /*************************************************************************** * exclusion clause for future posts **************************************************************************/ if (!preg_match('/2.1/',$options['wp_version'])) { $this->only_published = " (p.post_date_gmt <= '{$this->where_date}') "; $this->get_posts_clause = " (p.post_status = 'publish') "; $this->get_pages_clause = " (p.post_status = 'static') "; } else { $this->get_posts_clause = " (p.post_type = 'post') "; $this->get_pages_clause = " (p.post_type = 'page') "; } if (is_null($context['type']) || is_null($context['filter'])) { $requestvars = array( 'p'=>'post', 'pi'=>'post_id', 'c'=>'category', 'a'=>'archives', // LP vars 's'=>'static', 'q'=>'search', 'u' =>'author', 'y'=>'archives', 'tag'=>'tag',// LP vars 'm'=>'archives', 'postname'=>'post', 'category_name'=>'category', // old WP vars 'pagename'=>'static', 'year'=>'archives', 'page_id'=>'static', 'query'=>'search'); // old WP vars $req = array_merge($_POST, $_GET); foreach ($requestvars as $k=>$v) { if (isset($req[$k]) && !empty($req[$k])) { $context['type'] = $v; $context['filter'] = strip_tags($req[$k]); break; } } // fix static context so that we use the id if ($context['type'] == 'static') { $pages =& $this->getPages(); foreach ($pages as $k=>$v) { // no hierarchy, so we cannot check for duplicate post names if ($v['post_name'] == $context['filter']) { // make the tagcloud plugin work by simulating a hierarchical request $context['hierarchy'] = array($context['filter'], null, null); $context['filter'] = $v['id']; break; } } } /*************************************************************************** * nested contexts **************************************************************************/ if (empty($context['type']) && isset($_GET['y1'])) { $y1 = strip_tags($_GET['y1']); $y2 = (isset($_GET['y2']) ? strip_tags($_GET['y2']) : null); $y3 = (isset($_GET['y3']) ? strip_tags($_GET['y3']) : null); $parent = $subparent = $child = null; // try pages first $pages =& $this->getPages(); foreach ($pages as $k=>$v) { switch ($v['post_name']) { case $y1: $parent =& $pages[$k]; break; case $y2: if (isset($pages[$v['post_parent']]) && $pages[$v['post_parent']]['post_name'] == $y1) $subparent =& $pages[$k]; break; case $y3: if (!isset($pages[$v['post_parent']]) || $pages[$v['post_parent']]['post_name'] != $y2) break; $sp =& $pages[$v['post_parent']]; if (!isset($pages[$sp['post_parent']]) || $pages[$sp['post_parent']]['post_name'] != $y1) break; $child =& $pages[$k]; break; } } if ($child) { $context['type'] = 'static'; $context['filter'] = $child['id']; $context['url'] = $this->getPagePermalink( $child['id'], $child['post_name'], is_null($subparent) ? '' : $subparent['post_name'], is_null($parent) ? '' : $parent['post_name']); } else if ($subparent) { $context['type'] = 'static'; $context['filter'] = $subparent['id']; $context['url'] = $this->getPagePermalink( $subparent['id'], $subparent['post_name'], is_null($parent) ? '' : $parent['post_name'], ''); } else if ($parent) { $context['type'] = 'static'; $context['filter'] = $parent['id']; $context['url'] = $this->getPagePermalink( $parent['id'], $parent['post_name'], '', ''); } else { // try categories $categories =& $this->getCategories(); foreach ($categories as $k=>$v) { switch ($v['category_nicename']) { case rawurlencode($y1): $parent =& $categories[$k]; break; case rawurlencode($y2): $subparent =& $categories[$k]; break; case rawurlencode($y3): $child =& $categories[$k]; break; } } if ($child) { $context['type'] = 'category'; $context['filter'] = $child['cat_id']; } else if ($subparent) { $context['type'] = 'category'; $context['filter'] = $subparent['cat_id']; } else if ($parent) { $context['type'] = 'category'; $context['filter'] = $parent['cat_id']; } else { // TODO: output a customized error page if (isset($_SERVER['SERVER_PROTOCOL'])) header ($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); else header ('HTTP/1.0 404 Not Found'); } } // we might as well store the hierarchy, might be useful to plugins $context['hierarchy'] = array(&$parent, &$subparent, &$child); } if (ini_get('magic_quotes_gpc') == 1) $context['filter'] = stripslashes($context['filter']); } /*************************************************************************** * context index **************************************************************************/ if ($context['index'] == 0) { if (isset($_REQUEST['i']) && !empty($_REQUEST['i'])) { $context['index'] = (int)$_REQUEST['i']; if ($_REQUEST['i'] != '0' && $context['index'] == 0) $context['index'] = $_REQUEST['i']; } else if (isset($_REQUEST['paged']) && !empty($_REQUEST['paged'])) { $context['index'] = (int)$_REQUEST['paged'] * (int)$this->options['posts_per_page']; } } /*************************************************************************** * reqtype **************************************************************************/ if (empty($this->_reqtype) && isset($_GET['reqtype'])) $context['reqtype'] = $_GET['reqtype']; /*************************************************************************** * fix/check filter/url and set queryvars **************************************************************************/ switch ($context['type']) { case 'post': if ($this->raw_urls) { // use WP-style post permalink, e.g. index.php?p=id $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND p.id='{$context['filter']}'"; } else { $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND p.post_name='" . urlencode($context['filter']) . "'"; } // delay setting up context_url $context['url'] = null; break; case 'preview': $context['type'] = 'post'; $context['queryvars']['w'] = "AND (p.id='{$context['filter']}' OR p.post_name='{$context['filter']}')"; // delay setting up context_url $context['url'] = null; break; case 'post_id': $context['type'] = 'post'; $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND p.id='{$context['filter']}'"; // delay setting up context_url $context['url'] = null; break; case 'category': // we avoid a join in the posts query, and swap the shortname // so as to be able to access categories by index // a second loop since the index page is accessed more $categories =& $this->getCategories(); $found = false; if (!isset($categories[$context['filter']])) { // WP encodes the shortname in the DB, ugh! $nicename = rawurlencode($context['filter']); foreach ($categories as $cat_id => $category) { if ($category['category_nicename'] == $nicename) { $context['filter'] = $cat_id; $context['label'] = $category['cat_name']; $this->title .= " » " . $category['cat_name']; $found = true; break; } } } else { $found = true; $context['label'] = $categories[$context['filter']]['cat_name']; $this->title .= " » " . $categories[$context['filter']]['cat_name']; } if (!$found) { $context['type'] = 'post'; $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND p.id=''"; } else { $context['url'] = $categories[$context['filter']]['category_permalink']; $context['queryvars']['j'] .= " INNER JOIN " . $tables['post2cat'] . " p2c on p.id=p2c.post_id "; $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND p2c.category_id='{$context['filter']}'"; } break; case 'archives': if (!$this->raw_urls) { // fix context_filter if we have a 'y' or 'year' var if (isset($_REQUEST['y']) && isset($_REQUEST['m'])) { $context['filter'] .= strip_tags($_REQUEST['m']); } elseif (isset($_REQUEST['year']) && isset($_REQUEST['monthnum'])) { $context['filter'] .= strip_tags($_REQUEST['monthnum']); } } $archives =& $this->getArchives(); if (!in_array($context['filter'], array_keys($archives))) { $context['type'] = 'post'; $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND p.id=''"; } else { $archive =& $archives[$context['filter']]; $this->title .= " » " . $archive['label']; $context['label'] = $archive['label']; $context['url'] = $archive['permalink']; $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND year(p.post_date)=" . $archive['y'] . " AND month(p.post_date)=" . $archive['m'] . " "; } break; case 'static': $context['queryvars']['w'] = "AND {$this->get_pages_clause} AND p.id='{$context['filter']}'"; $context['queryvars']['j'] .= ' LEFT JOIN ' . $tables['posts'] . ' AS parent ON p.post_parent = parent.ID '; $context['queryvars']['f'] = ', parent.post_name AS parent_name '; // delay setting up context_url as we do not have the id yet $context['url'] = null; break; case 'search': $context['label'] = "{$this->messages['search_title']}: {$context['filter']}"; $this->title .= " » " . $this->messages['search_title']; $kw =& $this->_extractKeywords($context['filter']); if (count($kw) > 0) { $context['queryvars']['j'] .= " LEFT JOIN {$tables['tags']} AS tags ON (p.ID = tags.post_id) "; $context['queryvars']['w'] .= " AND ({$this->get_posts_clause} OR {$this->get_pages_clause}) AND ( (p.post_title LIKE '%" . implode("%' AND p.post_title LIKE '%", $kw) . "%') OR (p.post_content LIKE '%" . implode("%' AND p.post_content LIKE '%", $kw) . "%') OR (tags.tag_name='" . implode("' OR tags.tag_name='", $kw) . "') ) "; } if ($this->raw_urls) $context['url'] = 'index.php?q=' . urlencode($context['filter']); else $context['url'] = $options['search_prefix'] . '/' . urlencode($context['filter']); break; case 'author': $found = false; $this->db->query("select id, display_name from {$this->tables['users']}"); foreach ($this->db->all() as $row) { if ($row['display_name'] == $context['filter'] || $this->sanitize_user($row['display_name']) == $context['filter']) { if ($this->raw_urls) $context['url'] = 'index.php?u=' . urlencode($context['filter']); else $context['url'] = $options['author_prefix'] . '/' . $this->sanitize_user($context['filter']); $context['author_name'] = $context['filter']; $context['filter'] = $row['id']; $context['label'] = $row['display_name']; $this->title .= ' » ' . $context['filter']; $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND u.id={$context['filter']}"; $found = true; break; } } if (!$found) { $context['type'] = 'post'; $context['queryvars']['w'] = "AND {$this->get_posts_clause} AND p.id=''"; } break; case 'tag': $context['url'] = $this->getTagPermalink($context['filter']); $tags = $this->getTags(); $search_tag = str_replace(' ','+',$context['filter']); // search for plain tag $tag_found = false; foreach ($tags as $tag) { if ( $search_tag == $tag['name'] ) { $context['filter'] = $tag['name']; $tag_found = true; break; } } if (!$tag_found) { // check against ut8toascii tags foreach ($tags as $tag) { if ( $search_tag == $this->getTagEncoded($tag['name']) ) { $context['filter'] = $tag['name']; break; } } } $context['label'] = $context['filter']; $this->title .= ' » ' . $context['filter']; $context['url'] = $this->getTagPermalink($context['filter']); $context['queryvars']['j'] .= " LEFT JOIN {$tables['tags']} AS tags ON (p.ID = tags.post_id) "; $context['queryvars']['w'] = " AND ({$this->get_posts_clause} OR {$this->get_pages_clause}) AND (tags.tag_name='" . addslashes($context['filter']) . "') "; break; default: $context['queryvars']['w'] = "AND {$this->get_posts_clause}"; break; } if (!is_null($context['url'])) $context['url'] = $options['url'] . (($context['url'] != '') ? '/' : '') . $context['url']; return $context; } function getLastModified($check_comments=false) { $db =& $this->db; $tables =& $this->tables; list($qv_f, $qv_j, $qv_w, $qv_g) = array_values($this->context['queryvars']); $field = "p.post_modified_gmt"; if ($check_comments) { $field = "c.comment_date_gmt"; $qv_j .= " RIGHT JOIN " . $tables['comments'] . " c ON c.comment_post_id=p.id "; } $q = "SELECT UNIX_TIMESTAMP(MAX($field)) as tstamp, MAX($field) as lastmoddate FROM {$tables['posts']} p $qv_j WHERE {$this->only_published} $qv_w"; $db->query($q); $row = $db->next(); if ($row == null || !isset($row['tstamp'])) // no timestamp, generate one and subtract gmt_offset return time() - $this->options['gmt_offset']; else return (int)$row['tstamp']; // GMT timestamp } function &getPosts($context_type=null, $context_filter=null, $start_record=null, $num_records=null, $force=false, $simple=false) { $context = null; // TODO: fix the following if ((!is_null($context_type) && $context_type != $this->context['type']) || (!is_null($context_filter) && $context_filter != $this->context['filter'])) $context =& $this->getContext($context_type, $context_filter); if (is_null($context)) { // check if we can return the cached result if (!is_null($this->_posts) && !$force) return $this->_posts; else $context =& $this->context; } $context_type =& $context['type']; $context_filter =& $context['filter']; $options =& $this->options; $tables =& $this->tables; // render plugins $this->_processPlugins('get_posts', $context['queryvars']); list($f, $j, $w, $g, $o) = array_values($context['queryvars']); if (is_null($num_records)) $num_records = $options['posts_per_page']; if (is_null($start_record)) $start_record = $context['index']; $set_title = (($context_type == 'post') || ($context_type == 'static')); /******************************************************************* * create the context-dependent query ******************************************************************/ if ($context_type == 'post' || $context_type == 'static') // save the count(*) query $num_records = 1; /******************************************************************* * retrieve posts ******************************************************************/ $q = "SELECT p.id as post_id, p.post_author as post_author_id, UNIX_TIMESTAMP(p.post_date_gmt) + '" . $this->options['gmt_offset'] . "' as post_tstamp, UNIX_TIMESTAMP(p.post_date_gmt) as post_tstamp_gmt, p.post_excerpt, p.post_title, p.post_content, p.post_content_filtered, p.post_status, p.comment_status, p.ping_status, p.post_name, p.guid as post_guid, p.post_category, p.comment_count as post_comments, u.display_name as user_display_name, u.user_nicename, u.user_login as post_author_login $f FROM " . $tables['posts'] . " p $j WHERE {$this->only_published} $w GROUP BY p.id, p.post_author, post_tstamp, post_tstamp_gmt, p.post_title, p.post_content, p.post_status, p.comment_status, p.ping_status, p.post_name, p.guid, p.post_category, u.display_name, u.user_nicename, u.user_login $g $o " . (($context_type != 'post' && $context_type != 'static' && $num_records > 0) ? "LIMIT $start_record, $num_records" : ""); $this->db->query($q); $posts = $this->db->all(); /******************************************************************* * store posts count ******************************************************************/ $num_posts = $this->db->count; // used also in the posts loop below switch ($num_records) { case 0: case 1: $this->num_posts = $num_posts; if (is_null($this->context['url'])) { // might have been set when checking the hierarchy context $this->context['url'] = $options['url']; // no posts, set it to the index URL if (($num_records == 1) && isset($posts[0])) { if ($posts[0]['post_status'] == 'static') $this->context['url'] .= '/' . $this->getPagePermalink($posts[0]['post_id'], $posts[0]['post_name']); else $this->context['url'] .= '/' . $this->getPermalink($posts[0]['post_id'], $posts[0]['post_name'], $posts[0]['post_tstamp'], $posts[0]['post_category']); } } if (empty($context['label']) && count($posts)) $context['label'] = $posts[0]['post_title']; break; default: $q = "SELECT count(*) as num from " . $tables['posts'] . " p $j WHERE {$this->only_published} $w"; $this->db->query($q); $this->num_posts = (int)$this->db->get('num'); } if ($num_posts == 0) return $posts; /******************************************************************* * create the where filter for later queries (e.g. categories and comments) ******************************************************************/ $posts_in = array(); foreach ($posts as $post) $posts_in[] = $post['post_id']; // store in context as well $this->posts_in = $posts_in = implode(',', $posts_in); if (!$simple) { /******************************************************************* * retrieve posts categories ******************************************************************/ $q = "select distinct post_id, category_id from " . $tables['post2cat'] . " " . "where post_id in ($posts_in) order by post_id, category_id"; $this->db->query($q); $posts_categories = array(); foreach ($this->db->all() as $p2c) { $post_id = $p2c['post_id']; if (!isset($posts_categories[$post_id])) $posts_categories[$post_id] = array(); $posts_categories[$post_id][] = $p2c['category_id']; } /******************************************************************* * retrieve posts' tags ******************************************************************/ /* $q = "SELECT DISTINCT post_id, tag_name AS name FROM {$tables['tags']} tag WHERE post_id IN ({$this->posts_in}) ORDER BY post_id, name"; $this->db->query($q); $posts_tags = array(); foreach ($this->db->all() as $tag) { $post_id = $tag['post_id']; if (!isset($posts_tags[$post_id])) $posts_tags[$post_id] = array(); $posts_tags[$post_id][] = $tag['name']; } */ } /******************************************************************* * loop through posts and fill in missing attributes ******************************************************************/ $messages =& $this->messages; for ($i=0; $i<$num_posts;$i++) { $post =& $posts[$i]; $tstamp = (int)$post['post_tstamp']; $post['post_excerpt'] = stripslashes($post['post_excerpt']); if ( $this->options['use_preformatter'] && !empty($post['post_content_filtered']) && substr($post['post_content_filtered'], -1) == chr(0) ) { $content = explode('', stripslashes(substr($post['post_content_filtered'], 0, -1)), 2); $post['post_content'] = $content[0]; $post['post_more'] = (count($content) > 1) ? '' . $content[1] : ''; } else { $content = explode('', stripslashes($post['post_content']), 2); $post['post_content'] = $this->paragrapher($content[0]); $post['post_more'] = (count($content) > 1) ? '' . $this->paragrapher($content[1]) : ''; } $post['post_title'] = stripslashes($post['post_title']); if ($set_title) $this->title .= ' » ' . $post['post_title']; $post['post_title_xmlsafe'] = htmlspecialchars($post['post_title'], ENT_QUOTES); $post['post_date'] = strftime($options['date_format'], $tstamp); $post['post_short_date'] = strftime($options['short_date_format'], $tstamp); $post['post_time'] = strftime($options['time_format'], $tstamp); $post['post_short_time'] = strftime($options['short_time_format'], $tstamp); $post['post_tstamp'] = $tstamp; if ($post['post_status'] == 'static') $post['post_permalink'] = $this->getPagePermalink($post['post_id'], $post['post_name']); else $post['post_permalink'] = $this->getPermalink($post['post_id'], $post['post_name'], $tstamp, $post['post_category']); $post['post_year'] = strftime("%Y", $tstamp); $post['post_month'] = strftime("%m", $tstamp); $post['post_day'] = strftime("%d", $tstamp); $post['post_author'] = empty($post['user_display_name']) ? $post['user_nicename'] : $post['user_display_name']; $post['post_author_ascii'] = $this->sanitize_user($post['user_display_name']); if (!$simple) { $post['post_comments_label'] = (int)$post['post_comments'] == 1 ? $messages['comment'] : $messages['comments']; if ($context_type != 'static' && isset($posts_categories[$post['post_id']])) $post['categories'] =& $posts_categories[$post['post_id']]; else $post['categories'] = array(); /* if (isset($posts_tags[ $post['post_id'] ])) $post['tags'] = $posts_tags[ $post['post_id'] ]; else $post['tags'] = array(); */ } $post['post_class_suffix'] = ($i == ($num_posts - 1)) ? '_last' : ''; // render plugins $this->_processPlugins('get_posts_loop', $post); } if (is_null($context)) { $this->_posts =& $posts; $this->_post_date = null; return $this->_posts; } else { return $posts; } } function &getPages() { if (is_null($this->_pages)) { $q = "select p.id, p.post_name, p.post_title, p.post_parent, " . "UNIX_TIMESTAMP(p.post_date_gmt) + '" . $this->options['gmt_offset'] . "' as post_tstamp " . "from " . $this->tables['posts'] . " p " . "where {$this->get_pages_clause} and {$this->only_published} " . "order by p.post_parent, p.post_title"; $this->db->query($q); $_pages =& $this->db->all(); $pages = array(); $deferred = array(); foreach ($_pages as $k=>$page) { $parent = $page['post_parent']; $_pages[$k]['root_page'] = empty($parent); $_pages[$k]['children'] = array(); if (!empty($parent)) { if (isset($pages[$parent])) $pages[$parent]['children'][$page['post_title']] =& $_pages[$k]; else $deferred[$page['id']] = array($page['post_title'], $parent); } $pages[$page['id']] =& $_pages[$k]; } foreach ($deferred as $id=>$details) { list($title, $parent) = $details; if (isset($pages[$parent])) { $pages[$parent]['children'][$title] =& $pages[$id]; } else $pages[$id]['parent'] = ''; } // we use the title as a key for children to ease sorting, // but we don't sort here to skip a second recursion that // as it may not be used in the current context $this->_pages =& $pages; } return $this->_pages; } function getPagePermalink($post_id, $post_name, $parent_name=null, $ancestor_name=null) { if ($this->raw_urls) return 'index.php?page_id=' . $post_id; $options =& $this->options; if (!empty($options['pages_prefix'])) return $options['pages_prefix'] . '/' . $post_name . $options['pages_suffix']; // build hierarchy if (is_null($parent_name) || is_null($ancestor_name)) { $pages =& $this->getPages(); if (!isset($pages[$post_id])) return; $tokens = array(); $id = $post_id; while (isset($pages[$id])) { $tokens[] = $pages[$id]['post_name']; $id = $pages[$id]['post_parent']; } $tokens = array_reverse($tokens); } else { $tokens = array(); if (!empty($ancestor_name)) $tokens[] = $ancestor_name; if (!empty($parent_name)) $tokens[] = $parent_name; $tokens[] = $post_name; } return implode('/', $tokens) . $options['pages_suffix']; } function getPermalink($post_id, $post_name, $post_tstamp, $category_id) { $options =& $this->options; if ($this->raw_urls) return 'index.php?p=' . $post_id; $category_name = 'uncategorized'; if ($this->options['post_permalink_category_name']) { $categories =& $this->getCategories(); if (isset($categories[$category_id])) $category_name = $categories[$category_id]; } return sprintf( strftime($this->options['post_permalink_struct'], $post_tstamp), $post_name, $post_id, $category_id, $category_name); } function &getExcerpt(&$text, $maxlength=0, $continue='…') { // treat text as if it were post_content, assume slashes have already been stripped foreach (explode('', $text) as $t) { $buffer =& $t; break; } $buffer = strip_tags($buffer); if ($maxlength) { $tokens = preg_split('/\s+/u', $buffer); if (count($tokens) > $maxlength) $buffer = implode(' ', array_slice($tokens, 0, $maxlength)) . $continue; } return $buffer; } function &getComments($post_id=null, $start_record=null, $num_records=null) { $payload = array('start'=>$start_record, 'num'=>$num_records, 'post_id'=>$post_id); // render plugins $this->_processPlugins('get_comments', $payload); $start_record = $payload['start']; $num_records = $payload['num']; $q = "select *, UNIX_TIMESTAMP(comment_date_gmt) + '" . $this->options['gmt_offset'] . "' as comment_timestamp, UNIX_TIMESTAMP(comment_date_gmt) as comment_tstamp_gmt from " . $this->tables['comments'] . " where " . (!is_null($post_id) ? "comment_post_id=" . $post_id : '') . " and comment_approved='1' order by comment_date"; if (!is_null($num_records)) { $start_record = is_null($start_record) ? 0 : $start_record; $q .= " limit $start_record, $num_records"; } $db =& $this->db; $db->query($q); $comments =& $db->all(); return $comments; // avoid the stupid PHP warning } function &getPostTags($postid=null, $force=false) { /* * caches all tags matching current post selection * * if you call this method manually before the parse methods, the * structures are cached and reused later */ if (is_null($this->_posttags) || $force) { if (!isset($this->posts_in)) $this->getPosts(); // force posts cache load if (empty($this->posts_in)) { $this->_posttags = array(); return $this->_posttags; } $tables =& $this->tables; $q = "SELECT DISTINCT post_id, tag_name AS name FROM {$tables['tags']} tag WHERE post_id IN ({$this->posts_in}) ORDER BY post_id, name"; $this->db->query($q); $posts_tags = array(); foreach ($this->db->all() as $tag) { $post_id = $tag['post_id']; if (!isset($posts_tags[$post_id])) $posts_tags[$post_id] = array(); $posts_tags[$post_id][] = $tag['name']; } $this->_posttags =& $posts_tags; } if (is_null($postid)) return $this->_posttags; elseif (isset($this->_posttags[$postid])) return $this->_posttags[$postid]; else { $ret = array(); return $ret; } } function getTagEncoded($tagname) { return(str_replace('%2F', '/', urlencode($this->sanitize_tags($tagname)))); } function getTagPermalink($tagname) { return ( ($this->raw_urls) ? 'index.php?tag=' : $this->options['tag_prefix'] . '/') . $tagname; } function &getTags($force=false) { /* * gets all site tags * * if you call this method manually before the parse methods, the * structures are cached and reused later */ if (is_null($this->_tags) || $force) { $tables =& $this->tables; $q = "SELECT tag.tag_name AS name, COUNT(tag.post_id) as numposts, UNIX_TIMESTAMP(max(p.post_date_gmt)) + '" . $this->options['gmt_offset'] . "' as last_post_date, UNIX_TIMESTAMP(max(p.post_date_gmt)) as last_post_date_gmt FROM {$tables['tags']} tag INNER JOIN {$tables['posts']} p on tag.post_id=p.id WHERE ({$this->get_posts_clause} OR {$this->get_pages_clause}) AND {$this->only_published} GROUP BY tag.tag_name ORDER BY numposts DESC "; $this->db->query($q); $tags = array(); foreach ($this->db->all() as $tag) { $tags[$tag['name']] = $tag; } $this->_tags =& $tags; } return $this->_tags; } /*************************************************************************** * render methods **************************************************************************/ function startRender($content=null, $header=null, $sidebar=null, $footer=null) { if (!$this->_templates_set) { $templates =& $this->templates; if (is_null($content)) { switch ($this->context['type']) { case 'search': case 'author': case 'tag': case 'static': case 'post': $templates['index'] = $this->context['type'] . '.xml'; break; default: // already set in $this->templates; //$templates['index'] = 'index.xml'; } } else { $templates['index'] = $content; } if (!is_null($header)) $templates['header'] = $header; if (!is_null($sidebar)) $templates['sidebar'] = $sidebar; if (!is_null($footer)) $templates['footer'] = $footer; $this->_processPlugins('start_render', $templates); $this->tpl->setFile($templates); $this->_templates_set = true; } } function renderPost(&$post, $parse_block=true) { $this->current_post =& $post; $tpl =& $this->tpl; $context =& $this->context; $this->startRender(); if ($parse_block && !$this->_block_post) { // set blocks $tpl->setBlock('index', 'post', 'BLOCK_POST'); // TODO: make the following optional? $tpl->setBlock('post', 'day', 'BLOCK_DAY'); $tpl->setBlock('post', 'post_category', 'BLOCK_POST_CATEGORY'); $tpl->setBlock('post', 'post_more', 'BLOCK_POST_MORE'); $this->_block_post = true; } $post['skip_form'] = $post['skip_comments'] = false; /* * display the date label if necessary, or hide its template slot */ $static = false; switch ($context['type']) { case 'static': $static = true; case 'post': // hide the day block $tpl->setVar('BLOCK_DAY', ''); // hide the 'read more' link $tpl->setVar('BLOCK_POST_MORE', ''); // display comments $this->renderComments($post); break; default: // index pages, check date change if ($post['post_date'] != $this->_post_date) { // display the date $tpl->setVar('day_date', $post['post_date']); $tpl->parse('BLOCK_DAY', 'day'); $this->_post_date = $post['post_date']; $post['post_class_first'] = '_first'; } else { // hide the date $tpl->setVar('BLOCK_DAY', ''); $post['post_class_first'] = ''; } } /* * display post categories */ if (!$static) { // categories $multicategory = false; $categories =& $this->getCategories(); $i = count($post['categories']); $sep = $this->options['category_separator']; foreach ($post['categories'] as $post_category) { if (isset($categories[$post_category]['cat_name'])) { $tpl->setVar(array( 'post_cat_id' => $post_category, 'post_cat_name' => $categories[$post_category]['cat_name'], 'post_cat_nicename' => $categories[$post_category]['category_nicename'], 'post_cat_permalink' => $categories[$post_category]['category_permalink'], 'post_cat_separator' => ($i != 1 ? $sep : ''))); $tpl->parse('BLOCK_POST_CATEGORY', 'post_category', $multicategory); $multicategory = true; $i--; } } } else { $tpl->setVar('BLOCK_POST_CATEGORY', ''); } $this->_processPlugins('parse_post', $post); $tpl->setVar($post); if ($context['type'] != 'post' && $context['type'] != 'static') { // hide or show the 'read more' link if (empty($post['post_more'])) $tpl->setVar('BLOCK_POST_MORE', ''); else $tpl->parse('BLOCK_POST_MORE', 'post_more'); } $tpl->parse('BLOCK_POST', 'post', true); } function renderComments(&$post) { $tpl =& $this->tpl; $tpl->setVar('comments_list_start', '1'); $this->_processPlugins('render_comments', $post); if (!$tpl->setBlock('index', 'comments', 'BLOCK_COMMENTS', false)) return; if ($post['skip_comments']) { $tpl->setVar('BLOCK_COMMENTS', ''); return; } $comments =& $this->getComments($post['post_id']); if (count($comments) == 0) { $tpl->setBlock('comments', 'comments_list', 'BLOCK_COMMENTS_LIST'); $tpl->setVar('BLOCK_COMMENTS_LIST', ''); $tpl->setVar( "comments_message_text", ($post['comment_status'] == 'open') ? $this->messages['comments_open_nocomments'] : $this->messages['comments_closed_nocomments']); } else { $tpl->setBlock('comments', 'comments_message', 'BLOCK_COMMENTS_MESSAGE'); $tpl->setVar('BLOCK_COMMENTS_MESSAGE', ''); $tpl->setBlock('comments', 'comment', 'BLOCK_COMMENT'); $tpl->setBlock('comment', 'author_url', 'BLOCK_COMMENT_AUTHOR_URL'); $i = 1; foreach ($comments as $comment) { $comment['comment_content'] = stripslashes($comment['comment_content']); $comment['comment_content'] = $this->paragrapher($comment['comment_content']); $comment['comment_content'] = $this->close_comment_tags($comment['comment_content']); $comment_timestamp = $comment['comment_timestamp']; $comment['comment_short_date'] = strftime($this->options['short_date_format'], $comment_timestamp); $comment['comment_short_time'] = strftime($this->options['short_time_format'], $comment_timestamp); $comment['comment_date'] = strftime($this->options['date_format'], $comment_timestamp); $comment['comment_time'] = strftime($this->options['time_format'], $comment_timestamp); $comment['comment_index'] = $i; $comment['comment_class'] = ($i % 2 == 0) ? 'comment_even' : 'comment_odd'; // render plugins $this->_processPlugins('parse_comment', $comment); $tpl->setVar($comment); if (empty($comment['comment_author_url'])) $tpl->setVar('BLOCK_COMMENT_AUTHOR_URL', $comment['comment_author']); else $tpl->parse('BLOCK_COMMENT_AUTHOR_URL', 'author_url'); $tpl->parse('BLOCK_COMMENT', 'comment', true); $i++; } } $tpl->parse('BLOCK_COMMENTS', 'comments'); } function renderSidebar() { if (is_string($this->_sidebar)) return $this->_sidebar; $this->startRender(); $tpl =& $this->tpl; if ($this->context['type'] == 'search') $tpl->setVar('search_keywords', htmlspecialchars($this->context['filter'], ENT_QUOTES, $this->options['charset'])); else $tpl->setVar('search_keywords', ''); if ($this->context['type'] == 'tag') { $tpl->setVar('search_tags', $this->context['filter']); $tpl->setVar('search_tags_encoded', str_replace('%2F', '/', urlencode($this->sanitize_tags($this->context['filter'])))); } else { $tpl->setVar('search_tags', ''); $tpl->setVar('search_tags_encoded', ''); } // render plugins $plugin_data = ''; $this->_processPlugins('sidebar', $plugin_data); $this->_sidebar = $tpl->parse('SIDEBAR', 'sidebar'); } function renderPage($parse_posts=true, $parse_sidebar=true, $do_output=true) { $this->startRender(); $plugin_data = array( 'parse_posts'=>$parse_posts, 'parse_sidebar'=>$parse_sidebar, 'do_output'=>$do_output); $this->_processPlugins('pre_render', $plugin_data); $tpl =& $this->tpl; if ($plugin_data['parse_posts']) { $posts =& $this->getPosts(); if (count($posts) == 0) { // return a 404 error if (isset($_SERVER['SERVER_PROTOCOL'])) header ($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); else header ('HTTP/1.0 404 Not Found'); // then output a page with the correct header, sidebar, etc. $this->tpl->setBlock('index', 'post', 'BLOCK_POST'); if (!$tpl->setBlock('index', 'comments', 'BLOCK_COMMENTS', false)) $tpl->setVar('BLOCK_COMMENTS', ''); $this->tpl->setVar('BLOCK_POST', $this->messages['no_posts']); $tpl->setVar('BLOCK_COMMENTS', ''); // hide plugins $dummyvar = ''; $this->_processPlugins('parse_post', $dummyvar, true); } else { foreach ($posts as $k => $v) { $this->renderPost($posts[$k]); } } } if ($plugin_data['parse_sidebar']) { $this->renderSidebar(); } $tpl->parse('HEADER', 'header'); $tpl->parse('FOOTER', 'footer'); if (!empty($this->context['label'])) $this->title = "{$this->context['label']} » {$this->options['blogname']}"; $tpl->setVar(array( 'title' => $this->title, 'context_label' => $this->context['label'])); $this->_processPlugins('post_render', $plugin_data); $out =& $tpl->parse('MAIN', 'index'); // add a hook here if we ever need to parse the output before sending it // $this->_processPlugins('pre_output', $out); if (!is_null($this->cache) && $this->cache->serve_pages) { // don't cache 404s if (!$plugin_data['parse_posts'] || count($posts) ) { $this->cache->setCache($out); } } if ($plugin_data['do_output']) echo $out; $this->_processPlugins('shutdown', $plugin_data); } /*************************************************************************** * utilities **************************************************************************/ function &getMailHelper() { if (!is_object($this->_mail_helper)) { require_once 'classes/MailHelper.php'; $o =& $this->options; $this->_mail_helper =& new MailHelper( $o['mail_from'], $o['mail_mailer'], (isset($o['mail_mailer_path']) ? $o['mail_mailer_path'] : null)); } return $this->_mail_helper; } /*************************************************************************** * comments stuff **************************************************************************/ function insertComment(&$comment, $xml_output=false) { $options =& $this->options; $tables =& $this->tables; $db =& $this->db; $this->_processPlugins('insert_comment', $comment); // fix url if (!empty($comment['url']) && !preg_match('/^[a-zA-Z]+:\/\//', $comment['url'])) $comment['url'] = 'http://' . $comment['url']; $comment['comment'] = strip_tags($comment['comment'], $options['allowed_tags']); $comment['comment'] = $this->stripAttrs($comment['comment']); $c_text = addslashes($comment['comment']); $gmdate = gmdate("Y-m-d H:i:s", $comment['date']); $post_age = $comment['date'] - $comment['post_tstamp'] ; if ( ( $comment['approved'] != 'spam' ) || ( $post_age < 30 * 24 * 3600 ) ) { if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { $ip = $_SERVER['REMOTE_ADDR']; } $q = "insert into {$tables['comments']} ( comment_post_ID, comment_author, comment_author_email, comment_author_url, comment_author_IP, comment_date, comment_date_gmt, comment_content, comment_approved, comment_agent, comment_type) VALUES ({$comment['post_id']}, '" . addslashes($comment['author']) . "', '" . addslashes($comment['email']) . "', " . "'" . addslashes($comment['url']) . "', '{$ip}', " . "'" . strftime("%Y-%m-%d %H:%M:%S", $comment['date']) . "', '$gmdate', '$c_text', " . "'{$comment['approved']}', '{$_SERVER['HTTP_USER_AGENT']}', '{$comment['type']}')"; $db->query($q, true); // TODO: move to the DB wrapper $comment['id'] = mysql_insert_id(); if ($comment['approved'] == 1) { $db->query("update {$tables['posts']} set comment_count = comment_count + 1 where id = {$comment['post_id']}"); // trigger the cache garbage collector if we have an active cache if (!is_null($this->cache) && $this->cache->active) { $url = '/' . $comment['post_permalink']; if (isset($options['cache_actions_all_pages']) && $options['cache_actions_all_pages'] == '1') { $this->cache->garbageCollect(null, true); } elseif ($post_age > 3 * 24 * 3600 ) { $this->cache->clearCachePostUri($url, true); } else { $this->cache->clearCachePostUriAll($url, true); } } } } } function stripAttrs($string, $allowed=array('title', 'href', '/')) { $m = array(); if (preg_match_all('/<([a-zA-Z]+)\s+([^>]+)>/sS', $string, $m, PREG_SET_ORDER)) { foreach ($m as $match) { $tag = '<' . $match[1]; foreach (preg_split('/\s+/', $match[2], -1, PREG_SPLIT_NO_EMPTY) as $attr) { $attr_tokens = explode('=', $attr); if (in_array($attr_tokens[0], $allowed)) { $tag .= ' ' . $attr; } } $tag .= '>'; $string = str_replace($match[0], $tag, $string); } } return $string; } function linebreaker(&$s) { if (!preg_match('@<[^>]+\n@sm', $s)) return str_replace("\n", "
\n", $s); $buffer = ''; $inside = false; for ($i=0;$i': $inside = false; $buffer .= $c; break; case "\n": $buffer .= ($inside ? "\n" : "
\n"); break; default: $buffer .= $c; } } return $buffer; } function ¶grapher(&$s) { if (substr($s, 0, 4) == "\t

") return($s); // already formatted // clean up bare & $s = preg_replace('@&(?![a-z0-9#]+;)@', '&', $s); // code should be inside pre to be preformatted, let's leave it here for now anyway $block_tags = 'object|pre|p|dl|div|noscript|script|blockquote|form|table|td|th|ins|fieldset|address|h1|h2|h3|h4|h5|h6|ul|ol|li|code|textarea'; $r = '@(?: # match paragraph mark ((?:\n\s*){2,}) | # match block open tag (< # save tag (' . $block_tags . ') # save tag name [^>]*>) | # match block close tag () )@smix'; $m = array(); preg_match_all($r, str_replace("\r", '', $s), $m, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); $matches = count($m); $s = str_replace("\r", '', $s); if ($matches == 0) { $buffer = "

" . Frontend::linebreaker($s) . "

"; return $buffer; } else { $p_parents = array('div'=>1, 'blockquote'=>1, 'td'=>1, 'th'=>1, 'ins'=>1, 'form'=>1, 'li'=>1); $buffer = ''; $pre = false; $last_offset = 0; $tags = array(); $tag = null; $tag_content = ''; $reset_tag = null; foreach ($m as $match) { // grab the content from the latest match offset up to the current one $offset = $match[0][1]; $slice = substr($s, $last_offset, $offset - $last_offset); $convert = is_null($tag) || (!$pre && isset($p_parents[$tag])); if ($convert) { $slice = trim($slice); $slice = Frontend::linebreaker($slice); } $last_offset = $offset + strlen($match[0][0]); // now fill the buffer if (!empty($slice)) $buffer .= $convert ? "

$slice

\n" : "$slice"; // set the current tag context switch (count($match)) { case 2: # paragraph mark $buffer .= $pre ? "\n\n" : ''; break; case 4: # block open tag $tag = strtolower($match[3][0]); $tags[] = $tag; if ($tag == 'pre' || $tag == 'script') $pre = true; $buffer .= $match[0][0] . ($pre ? '' : "\n"); break; case 6: # block close tag array_pop($tags); $tag = strtolower($match[5][0]); if ($tag == 'pre' || $tag == 'script') $pre = false; $buffer .= $match[0][0] . ($pre ? '' : "\n"); if ($tag == 'pre') $pre = false; $tlen = count($tags); if ($tlen > 0) $tag = $tags[$tlen - 1]; else $tag = $pre = null; break; } } $tail = substr($s, $last_offset, strlen($s) - $last_offset); if ($pre) { // unlikely, but does not hurt to check $buffer .= $tail; } else { $tail = trim($tail); if (!empty($tail)) $buffer .= '

' . Frontend::linebreaker($tail) . '

'; } } return $buffer; } function setUserCookie($user_data) { $cookiepath = preg_replace('|https?://[^/]+|i', '', $this->options['url'] . '/'); foreach ($user_data as $k=>$v) { switch ($k) { case 'ud_name': $user_data[$k] = urlencode($v); case 'ud_email': case 'ud_url': break; default: unset($user_data[$k]); } } setcookie('lp_identity', serialize($user_data), time() + 30000000, $cookiepath); } function removeUserCookie() { $cookiepath = preg_replace('|https?://[^/]+|i', '', $this->options['url'] . '/'); setcookie('lp_identity', '', time() + 30000000, $cookiepath); } function &getUserPerms($filter='', $extra_fields=null) { $q = "select u.id, u.user_email, u.user_login, um.meta_key, um.meta_value"; if (is_array($extra_fields)) $q .= ', ' . implode(', ', $extra_fields); $q .= " from {$this->tables_prefix}_users u inner join {$this->tables_prefix}_usermeta um on u.ID = um.user_id where "; if (!empty($filter)) $q .= "$filter and "; $q .= "um.meta_key in ('{$this->tables_prefix}_user_level', '{$this->tables_prefix}_capabilities')"; $users = array(); if (!$this->db->query($q)) return $users; foreach ($this->db->all() as $row) { $id = $row['id']; if (!isset($users['id'])) { $users[$id] = array( 'user_email'=>$row['user_email'], 'user_login'=>$row['user_login'], 'user_level'=>null, 'capabilities'=>array()); if (is_array($extra_fields)) { foreach ($extra_fields as $field) $users[$id][$field] = $row[$field]; } } $meta_key = substr($row['meta_key'], strlen($this->tables_prefix) + 1); $users[$id][$meta_key] = $row['meta_value']; } foreach ($users as $k=>$v) { if (!is_int($users[$k]['user_level'])) $users[$k]['user_level'] = (int)$users[$k]['user_level']; if (!is_array($users[$k]['capabilities'])) $users[$k]['capabilities'] = unserialize($users[$k]['capabilities']); } return $users; } function userFromWpCookie() { if (!is_null($this->_user_from_cookie)) return $this->_user_from_cookie; $cookie_hash = md5($this->options['wp_url']); $user_cookie = "wordpressuser_$cookie_hash"; $pass_cookie = "wordpresspass_$cookie_hash"; if (!isset($_COOKIE[$user_cookie]) || !isset($_COOKIE[$pass_cookie])) return; $user_login = $_COOKIE[$user_cookie]; $user_md5_pass = $_COOKIE[$pass_cookie]; $users =& $this->getUserPerms("u.user_login = '$user_login' and md5(u.user_pass) = '$user_md5_pass'"); if (count($users) == 0) return; $this->_user_from_cookie = array_shift($users); return $this->_user_from_cookie; } function redirectToPost($post_permalink, $message) { // redirect user so that he does not insert the comment twice header( "Location: " . $this->options['url'] . '/' . $post_permalink . '?' . 'message=' . urlencode($message) . '#add_comment'); exit; } function &_extractKeywords($q, $force=false) { if (!$force && !is_null($this->search_keywords)) return $this->search_keywords; // sanitize $q = trim($q); $q = preg_replace('/\s+/', ' ', $q); // extract keywords $keywords = array(); $inside = false; $keyword = ''; for ($i = 0; $i < strlen($q); $i++) { $c = $q[$i]; if ($inside) { if ($c == '"') $inside = false; else $keyword .= $c; } else { switch ($c) { case ' ': if (!empty($keyword)) $keywords[] = addslashes(strtolower($keyword)); $keyword = ''; break; case '"': $inside = true; break; default: $keyword .= $c; } } } if (!empty($keyword)) $keywords[] = addslashes(strtolower($keyword)); $this->search_keywords =& $keywords; return $keywords; } /*************************************************************************** * feeds stuff **************************************************************************/ function doConditionalGet($tstamp) { /* A PHP implementation of conditional get for LightPress, see also * http://fishbowl.pastiche.org/archives/001132.html * http://simon.incutio.com/archive/2003/04/23/conditionalGet * * for HTTP/1.1 dates see RFC2616#3.3.1 * * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format * All HTTP date/time stamps MUST be represented in Greenwich Mean Time * (GMT), without exception. * HTTP/1.1 clients and servers that parse the date value MUST accept * all three formats (for compatibility with HTTP/1.0), though they MUST * only generate the RFC 1123 format for representing HTTP-date values * in header fields. * * and #14.25 for If-Modified-Since * * Note: When handling an If-Modified-Since header field, some * servers will use an exact date comparison function, rather than a * less-than function, for deciding whether to send a 304 (Not * Modified) response. To get best results when sending an If- * Modified-Since header field for cache validation, clients are * advised to use the exact date string received in a previous Last- * Modified header field whenever possible. * * etc. * */ // ETag is any quoted string $etag = '"'. $tstamp .'"'; // RFC1123 date, see http://bugs.php.net/bug.php?id=31842 if (version_compare(PHP_VERSION, "4.3.11", ">=")) $format = 'r'; else $format = 'D, d M Y H:i:s O'; $rfc1123 = substr(date($format, $tstamp), 0, -5) . 'GMT'; // RFC1036 date $rfc1036 = date('l, d-M-y H:i:s ', $tstamp) . 'GMT'; // asctime $ctime = date('D M j H:i:s', $tstamp); // Send the headers header("Last-Modified: $rfc1123"); header("ETag: $etag"); // See if the client has provided the required headers $if_modified_since = $if_none_match = false; if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) $if_modified_since = stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']); if(isset($_SERVER['HTTP_IF_NONE_MATCH'])) $if_none_match = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); if (!$if_modified_since && !$if_none_match) { // both are missing return $rfc1123; } // At least one of the headers is there - check them // check etag if it's there and there's no if-modified-since if ($if_none_match) { if ($if_none_match != $etag) { // etag is there but doesn't match return $rfc1123; } if (!$if_modified_since && ($if_none_match == $etag)) { header('HTTP/1.0 304 Not Modified'); exit; } } if ($if_modified_since) { // check if-modified-since foreach (array($rfc1123, $rfc1036, $ctime) as $d) { if ($d == $if_modified_since) { // Nothing has changed since their last request - serve a 304 and exit header('HTTP/1.0 304 Not Modified'); exit; } } } // return $rfc1123 as it may be useful later, eg 'lastBuildDate' for RSS2 return $rfc1123; } function ISO8601($tstamp) { return date("Y-m-d\TH:i:s", $tstamp) . '+00:00'; } /*************************************************************************** * moved from attributes to (static) methods for the WP admin plugins **************************************************************************/ function getCriticalOptions() { return array('basedir', 'url', 'mail_from'); } function getBaseOptions() { return array( //'basedir' => dirname(__FILE__), // can't live without //'url' => 'http://myhost.org', // can't live without 'wp_url' => 'http://myhost.org/wordpress', // set by Frontend if not in config.php 'blogname' => 'Blog Name', 'shortname' => 'short_name', 'description' => 'Description for atom feeds.', 'copyright_notice' => 'Copyright notice for atom feeds.', 'posts_per_page' => 12, 'posts_per_rss' => 10, 'rss_use_excerpt' => '0', 'date_format' => '%A %d %B %Y', 'short_date_format' => '%d %b %Y', 'time_format' => '%H:%M:%S', 'short_time_format' => '%H:%M', 'archive_label' => '%B %Y', 'lang' => 'en_US', 'charset' => 'UTF-8', // UTF-8, ISO-8859-1, etc. 'template' => 'k1andahalf', 'approx_sql_tstamp' => 100, // where_date = round(where_date / approx_sql_tstamp) * approx_sql_tstamp 'mail_mailer' => false, // false or empty to use PHP's mail() function //'mail_from' => 'me@my.host', // can't live without 'mail_on_comment' => true, 'allowed_tags' => '


', 'allowed_tags_close'=> 'acronym b blockquote code em i strike strong', 'post_prefix' => 'post/{post_name}', 'category_prefix' => 'category', 'archives_prefix' => 'archives/{year}/{monthnum}', 'pages_prefix' => '', 'pages_suffix' => '', 'search_prefix' => 'search', 'author_prefix' => 'author', 'tag_prefix' => 'tag', 'comments_prefix' => 'comments', 'category_separator'=> ',', 'comment_spam_kill' => true, // true kills the supposed spam, false inserts it waiting for moderation 'comment_spam_mail' => true, // true mail on kills only 'use_preformatter' => false, 'preformatter_method' => 'wp', 'plugins' => array() ); } function getDefaultPlugins() { // try to list them all here so we can include the template tags for inactive plugins return array( // plugin active contexts args 'AboutContext' =>array(LP_CONTEXT_CATEGORY | LP_CONTEXT_ARCHIVES, array()), 'AboutMe' =>array(LP_CONTEXT_INDEX, array()), 'AboutPost' =>array(LP_CONTEXT_POST, array()), 'AdminOptions' =>array(LP_CONTEXT_ALL, array()), 'AltTemplate' =>array(0, array()), 'ArchivesList' =>array(0, array()), 'ArchivesListExtended' =>array(LP_CONTEXT_ALL, array()), 'AuthorIndex' =>array(LP_CONTEXT_AUTHOR, array()), 'CategoriesList' =>array(0, array()), 'CategoriesListExtended' =>array(LP_CONTEXT_ALL, array()), 'CommentsList' =>array(0, array()), 'CommentSpam' =>array(LP_CONTEXT_POST, array()), 'Delicious' =>array(0, array()), 'Flickr' =>array(0, array()), 'Gallery' =>array(0, array()), 'HighlightPost' =>array(0, array()), 'HighlightImage' =>array(0, array()), 'HeaderAd' =>array(0, array()), 'Links' =>array(LP_CONTEXT_ALL, array()), 'MetaDescription' =>array(0, array()), 'NestedPages' =>array(0, array()), 'Pagination' =>array(LP_CONTEXT_ALL, array()), 'PostComment' =>array(LP_CONTEXT_POST, array()), 'PostNavigation' =>array(0, array()), 'PostRelated' =>array(LP_CONTEXT_POST, array()), 'PostsList' =>array(LP_CONTEXT_ALL ^ LP_CONTEXT_INDEX, array()), 'PostTags' =>array(LP_CONTEXT_ALL, array()), 'RandomPosts' =>array(0, array()), 'SearchIndex' =>array(LP_CONTEXT_SEARCH, array()), 'SecondPage' =>array(0, array()), 'Stats' =>array(0, array()), 'TagCloud' =>array(LP_CONTEXT_STATIC, array()), 'TagMetaKeywords' =>array(0, array()), 'TopCommented' =>array(LP_CONTEXT_ALL, array()), 'TopRead' =>array(0, array()), ); } function getVersion() { return '2.0.0'; } function getPermalinkVerbs() { return array( 'post' => array( 'year' => '%Y', 'monthnum' => '%m', 'day' => '%d', 'hour' => '%H', 'minute' => '%M', 'second' => '%S', 'postname' => '%%1$s', 'post_name' => '%%1$s', 'post_id' => '%%2$s', 'category_id' => '%%3$s', 'category_name' => '%%4$s'), // 'author' => ''), // unsupported for now, since it requires an additional JOIN in plugins 'archives' => array( 'year' => '%Y', 'monthnum' => '%m', 'day' => '%d')); } function prefixToPermalinkStruct($value, $context='post') { if (empty($value)) return $value; $permalink_verbs = Frontend::getPermalinkVerbs(); if (!isset($permalink_verbs[$context])) return $value; $matches = array(); preg_match_all('/{([^}]+)}/', $value, $matches); foreach ($matches[1] as $i=>$verb) { if (isset($permalink_verbs[$context][$verb])) { $value = str_replace('{' . $verb . '}', $permalink_verbs[$context][$verb], $value); } } return $value; } function sanitize_user($user) { if (!utf8_is_ascii($user)) { $user = utf8_accents_to_ascii($user); $user = utf8_strip_non_ascii($user); } $user = strtolower($user); $user = preg_replace('/\s+/', '-', $user); return $user; } function sanitize_tags($tag) { if (!utf8_is_ascii($tag)) { $tag = utf8_accents_to_ascii($tag); $tag = utf8_strip_non_ascii($tag); } $tag = strtolower($tag); return $tag; } function close_comment_tags($content) { $options =& $this->options; $tags = split(' ',$options['allowed_tags_close']); $add_text = ''; foreach ($tags as $tag) { $pattern_open= "/<$tag\b[^>]*>/Ui"; $pattern_close= "/<\/$tag\b[^>]*>/Ui"; $o_count = preg_match_all ( $pattern_open, $content, $out ); $c_count = preg_match_all ( $pattern_close, $content, $out ); if ($o_count > $c_count) { $add_text .= str_repeat("", ($o_count - $c_count)); } } $content .= $add_text; return $content; } function raiseError($message, $method, $line, $type=E_USER_WARNING) { trigger_error( sprintf("%s.%s() line %d: %s", get_class($this), $method, $line, $message), $type); } } ?>