Source for file MinimalWeblog.php

Documentation is available at MinimalWeblog.php

  1. <?php
  2.  
  3. // MinimalWeblog 1.0.2
  4. // Copyright 2006 Jan Pieter Kunst <jpkunst at xs4all dot nl>
  5. // 
  6. // Based on Personal Weblog 1.0.0
  7. // http://www.kyne.com.au/~mark/software/weblog.php
  8. // Copyright 2001,2002  Mark Pulford <mark@kyne.com.au>
  9. //
  10. // This program is free software; you can redistribute it and/or modify 
  11. // it under the terms of the GNU General Public License as published by 
  12. // the Free Software Foundation; either version 2 of the License, or 
  13. // (at your option) any later version. 
  14. //  
  15. // This program is distributed in the hope that it will be useful, 
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of 
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  18. // GNU General Public License for more details. 
  19. //  
  20. // You should have received a copy of the GNU General Public License 
  21. // along with this program; if not, write to the Free Software 
  22. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  23.  
  24.  
  25. /**
  26.  * @package MinimalWeblog
  27.  * @version 1.0.2
  28.  */
  29.  
  30. /**
  31.  * Constant definitions, configuration for MinimalWeblog
  32.  */
  33. require('config.inc.php');
  34.  
  35. /**
  36.  * Class to make a connection to a MySQL database with the mysqli extension
  37.  */
  38. require('DB_MySQLi.class.php');
  39.  
  40. /**
  41.  * Class to handle weblog functionality; for embedding in existing web page
  42.  *
  43.  * MinimalWeblog 1.0.2 by Jan Pieter Kunst,
  44.  * based on {@link http://www.kyne.com.au/~mark/software/weblog.php Personal Weblog 1.0.0} by Mark Pulford
  45.  * @copyright MinimalWeblog (c) 2006 Jan Pieter Kunst <jan.pieter.kunst at xs4all dot nl>
  46.  * @copyright Personal Weblog (c) 2001,2002  Mark Pulford <mark at kyne dot com dot au>
  47.  * @license http://www.gnu.org/licenses/gpl.txt GNU General Public License
  48.  * @package MinimalWeblog
  49.  */
  50. class MinimalWeblog {
  51.     
  52.     /**
  53.      * @var bool gets set to TRUE if logged in with admin privileges
  54.      */
  55.     private $_admin = FALSE;
  56.     /**
  57.      * @var obj object of class DBMySQLi which handles the database stuff
  58.      */
  59.     private $_db;
  60.     /**
  61.      * @var string prefix for table names (useful if no separate db for weblog)
  62.      */
  63.     private $_prefix;
  64.     /**
  65.      * @var array options loaded from the database
  66.      */
  67.     private $_dbvars = array();
  68.     /**
  69.      * @var bool gets set to TRUE if the script cannot run
  70.      */
  71.     private $_broken = FALSE;
  72.     /**
  73.      * @var string name of the script. Basically $_SERVER['PHP_SELF']
  74.      */
  75.     private $_self;
  76.     /**
  77.      * @var array strings denoting encountered errors
  78.      */
  79.     private $_errors = array();
  80.     /**
  81.      * @var array acceptable numeric cgi variables (when prefixed with "wl_")
  82.      */
  83.     private $_numeric_cgi = array('topic''delopt''tid''eid''offset');
  84.     /**
  85.      * @var array acceptable string cgi variables (when prefixed with "wl_")
  86.      */    
  87.     private $_string_cgi = array('mode''name''icon''value''title''body''more''password''update''search');
  88.     /**
  89.      * @var array combination of valid numeric and string variables
  90.      */    
  91.     private $_valid_cgi = array();
  92.     /**
  93.      * @var int ordinal number of entry to start the current page with, used when paging through entries
  94.      */    
  95.     private $_offset = 0;
  96.     /**
  97.      * @var int id of current topic (when browsing)
  98.      */
  99.     private $_topic;
  100.     /**
  101.      * @var int id of topic where entries belonging to another, to-be-deleted topic will be moved into
  102.      */
  103.     private $_delopt;
  104.     /**
  105.      * @var int id of current entry
  106.      */
  107.     private $_eid;
  108.     /**
  109.      * @var int id of current topic (when editing)
  110.      */
  111.     private $_tid;
  112.     /**
  113.      * @var string current weblog mode
  114.      */
  115.     private $_mode;
  116.     /**
  117.      * @var string md5 hash of admin password
  118.      */
  119.     private $_password;
  120.     /**
  121.      * @var string name of current topic
  122.      */
  123.     private $_name;
  124.     /**
  125.      * @var string path to icon of current topic
  126.      */
  127.     private $_icon;
  128.     /**
  129.      * @var array configuration variables
  130.      */
  131.     private $_value;
  132.     /**
  133.      * @var string title of current weblog entry
  134.      */
  135.     private $_title;
  136.     /**
  137.      * @var string first part of current weblog entry
  138.      */
  139.     private $_body;
  140.     /**
  141.      * @var string second part of current weblog entry (optional)
  142.      */
  143.     private $_more;
  144.     /**
  145.      * @var string whether or not editing an existing entry should update its date
  146.      */
  147.     private $_update;
  148.     /**
  149.      * @var string text to search for in weblog entries
  150.      */
  151.     private $_search;
  152.  
  153.     /**
  154.      * Weblog constructor
  155.      *
  156.      * The Weblog class must be instantiated at the top of the page
  157.      * before any data is sent. This way it can modify the HTTP headers.
  158.      * Checks for a valid new or current login;
  159.      * Sets the class variable $_admin to true on a successful login.
  160.      */
  161.     public function __construct({        
  162.  
  163.         set_error_handler(array($this'handle_error'));
  164.         
  165.         if (version_compare(PHP_VERSION'5.1.0RC1''>=')) {
  166.         }
  167.  
  168.         $this->_self = $_SERVER['PHP_SELF'];
  169.         
  170.         $this->_valid_cgi = array_merge($this->_numeric_cgi$this->_string_cgi);
  171.  
  172.         if (headers_sent()) {
  173.             trigger_error('The Weblog constructor must be called before the headers have been sent'E_USER_ERROR);
  174.             $this->_broken = TRUE;
  175.             return;
  176.         }
  177.  
  178.         // Constants are defined in minimalweblog_defs.inc.php
  179.         $this->_prefix = MINIMALWEBLOG_TABLE_PREFIX;
  180.  
  181.         if (!$this->_db->connected()) {
  182.             trigger_error('Unable to connect to the database. Check Weblog() arguments.'E_USER_ERROR);
  183.             $this->_broken = TRUE;
  184.             return;            
  185.         }
  186.  
  187.         // get data from GET/POST and clean up
  188.         $this->load_cgi_vars(array_merge($_GET$_POST));
  189.         $this->sanitize_input();
  190.         // load options from the database
  191.         $this->load_db_vars();
  192.  
  193.         if ($this->_mode == 'rss'{
  194.             $this->rss_print();
  195.             exit;
  196.         }
  197.  
  198.         // Check for login status or log out
  199.         // Only used for admin user
  200.         if ($this->_mode == 'logout'{
  201.             $this->logout();
  202.         elseif ($this->current_login()) {
  203.             $this->_admin = TRUE;
  204.         elseif ($this->_password == $this->getvar('password')) {
  205.             // $this->_password only set after wl_mode=login called
  206.             // (from login form)
  207.             session_start();
  208.             $_SESSION['logged_in'TRUE;
  209.             $this->_admin = TRUE;
  210.         }
  211.         
  212.     }
  213.  
  214.     /**
  215.     * Return weblog HTML
  216.     *
  217.     * @return string HTML for chosen weblog mode
  218.     */
  219.     public function insert({        
  220.     
  221.         if ($this->_broken{
  222.             trigger_error('Cannot insert weblog HTML, Weblog is not initialised'E_USER_ERROR);
  223.             return '<p>' join('<br />'$this->_errors'</p>';
  224.         }
  225.         
  226.         $retval $this->getvar('header'"\n";
  227.         /**
  228.          * Keys of this array are possible values of the
  229.          * request variable 'wl_mode'. wl_mode sets $this->_mode.
  230.          * Values are names of private methods to be invoked
  231.          * depending on the value of $this->_mode.
  232.          */
  233.         $controller array(
  234.             'more' => 'entry_display',
  235.             'archive' => 'entry_archive',
  236.             'edit setup' => 'setup_edit',
  237.             'save setup' => 'setup_save',
  238.             'edit entry' => 'entry_edit',
  239.             'preview entry' => 'entry_preview',
  240.             'save entry' => 'entry_save',
  241.             'delete entry' => 'entry_delete',
  242.             'delete topic' => 'topic_delete',
  243.             'change topic' => '_topic_update',
  244.             'add topic' => 'topic_add',
  245.             'edit topics' => 'topic_edit',
  246.             'login' => 'login',
  247.             'logout' => 'entry_search'// actual logout has happened earlier
  248.         );
  249.  
  250.         // The configurable buttons have varying mode names
  251.         $controller[$this->getvar('search_text')'entry_search';
  252.         $controller[$this->getvar('reset_text')'entry_reset';
  253.  
  254.         if (!isset($this->_mode)) {
  255.             $retval .= $this->entry_search();
  256.         else if (isset($controller[$this->_mode])) {
  257.             $retval .= call_user_func(array($this$controller[$this->_mode]));            
  258.         else {
  259.             trigger_error('Unknown weblog mode'E_USER_ERROR);
  260.         }
  261.  
  262.         $retval .= $this->getvar('footer'"\n";
  263.  
  264.         if (empty($this->_errors)) {
  265.             $retval .= '<p>' join('<br />'$this->_errors'</p>';
  266.         }
  267.         
  268.         return $retval;
  269.     }
  270.  
  271.     /**
  272.      * Return HRTML form to log in as admin
  273.      *
  274.      * @return string HTML with form or error message
  275.      */
  276.     private function _login_form({
  277.         
  278.         $retval "<h2>Login</h2>\n";
  279.         
  280.         if (isset($this->_password)) {
  281.             trigger_error('Incorrect password'E_USER_WARNING);
  282.             return $retval;
  283.         }
  284.                 
  285.         $retval .= "<p><form action=\"{$this->_self}\" method=\"post\">\n";
  286.         $retval .= $this->print_form_vars(array_diff($this->_valid_cgi,
  287.                           array('password')));
  288.         $retval .= "<input type=\"password\" name=\"wl_password\">\n";
  289.         $retval .= "<input type=\"submit\" value=\"Login\">\n";
  290.         $retval .= "</form></p>\n";
  291.         $retval .= $this->_back_link();
  292.         
  293.         return $retval;
  294.     }
  295.  
  296.     /**
  297.      * Returns an URL query string
  298.      *
  299.      * @param array array of variables to put in query string
  300.      * @return string query string with same
  301.      */
  302.     private function _query_string($array) {
  303.         
  304.         $vars = array();
  305.         foreach ($array as $k => $v) {
  306.             if (! is_null($v)) {
  307.                 array_push($vars, "wl_$k=" . urlencode($v));
  308.             }
  309.         }
  310.  
  311.         return join('&', $vars);
  312.     }
  313.  
  314.     /**
  315.      * Returns an <a> element
  316.      *
  317.      * @param array array of variables to put in query string
  318.      * @param string text for link
  319.      * @return string HTML with <a> element
  320.      */
  321.     private function _href_link($array, $text) {
  322.  
  323.         $query_string = htmlspecialchars($this->_self . '?' $this->_query_string($array));
  324.         
  325.         return "<a href=\"$query_string\">$text</a>";
  326.     }
  327.  
  328.     /**
  329.      * Return a link back to the main page while maintaining current search parameters
  330.      *
  331.      * @return string HTML with link
  332.      */
  333.     private function _back_link() {
  334.         
  335.         $query_string = $this->_query_string(array('offset' => $this->_offset,
  336.                                          'topic' => $this->_topic,
  337.                                          'search' => $this->_search));
  338.         if ($query_string{
  339.             $query_string = '?' . $query_string;
  340.         }
  341.         $query_string = htmlspecialchars($this->_self . $query_string);
  342.         $bl $this->getvar('backlink');
  343.         
  344.         return str_replace('@URL@'$query_string$bl);
  345.     }
  346.  
  347.     /**
  348.      * Change name or icon of topic. wl_mode=change+topic
  349.      *
  350.      * Uses properties:  tid, name, icon
  351.      * @return string HTML with success or failure message
  352.      */
  353.     private function _topic_update() {        
  354.         
  355.         $retval = "<h2>Updating topic</h2>\n";
  356.  
  357.         if (! $this->_admin{
  358.             trigger_error('Not logged in', E_USER_WARNING);
  359.             return $retval;
  360.         }
  361.  
  362.         if (!$this->_tid{
  363.             trigger_error('No topic selected', E_USER_WARNING);
  364.             $retval .= $this->_back_link();
  365.             return $retval;
  366.         }
  367.  
  368.         $terms = '';
  369.         if ($this->_name{
  370.             $terms = 'name=?';
  371.             $params = array('name' => $this->_name);
  372.         }
  373.         if ($this->_icon{
  374.             if ($terms != '') {
  375.                 $terms .= ', ';
  376.             }
  377.             $terms .= 'icon=?';
  378.             $params['icon'] = $this->_icon;
  379.         }
  380.         if ($terms == '') {
  381.             trigger_error('At least one of icon or name must be set', E_USER_WARNING);
  382.             $retval .= $this->_back_link();
  383.             return $retval;
  384.         }
  385.         
  386.         $params['tid'] = $this->_tid;
  387.  
  388.         $numrows $this->_db->change("UPDATE {$this->_prefix}topics SET $terms WHERE tid=?", $params);
  389.         
  390.         if ($numrows == 1) {
  391.             $retval .= "<p><b>Topic changed</b></p>\n";
  392.         } else {
  393.             $retval .= "<p><b>Topic not changed</b></p>\n";
  394.         }
  395.  
  396.         $retval .= $this->_back_link();
  397.         
  398.         return $retval;
  399.     }
  400.  
  401.     /**
  402.      * Add a topic. wl_mode=add+topic
  403.      *
  404.      * Uses properties:  name, icon
  405.      * @return string HTML with success or failure message
  406.      */
  407.     private function topic_add() {
  408.         
  409.         $retval =  "<h2>Adding topic</h2>\n";
  410.  
  411.         if (! $this->_admin{
  412.             trigger_error('Not logged in', E_USER_WARNING);
  413.             return $retval;
  414.         }
  415.  
  416.         if (!$this->_name || !$this->_icon{
  417.             trigger_error('Both name and icon must be set', E_USER_WARNING);
  418.             $retval .= $this->_back_link();
  419.             return $retval;
  420.         }
  421.  
  422.         $query = "INSERT INTO {$this->_prefix}topics (nameiconVALUES (?,?)";
  423.         $params = array('name' => $this->_name'icon' => $this->_icon);
  424.         $numrows $this->_db->change($query$params);
  425.  
  426.         if ($numrows == 1)
  427.             $retval .= "<p><b>Topic added</b></p>\n";
  428.         else
  429.             trigger_error('Failed to add topic'E_USER_ERROR);
  430.  
  431.         $retval .= $this->_back_link();
  432.  
  433.         return $retval;
  434.     }
  435.  
  436.     /**
  437.      * Delete a topic, possibly moving entries to another topic. wl_mode=delete+topic
  438.      *
  439.      * Uses properties:  tid, delopt
  440.      * @return string HTML with success or failure message
  441.      */
  442.     private function topic_delete() {
  443.         
  444.         $retval = "<h2>Deleting topic</h2>\n";
  445.  
  446.         if (! $this->_admin{
  447.             trigger_error('Not logged in', E_USER_WARNING);
  448.             return $retval;
  449.         }
  450.  
  451.         if (!$this->_tid{
  452.             trigger_error('No topic selected', E_USER_WARNING);
  453.             $retval .= $this->_back_link();
  454.             return $retval;
  455.         }
  456.  
  457.         if ($this->_tid == $this->_delopt{
  458.             trigger_error('Cannot move entries into the topic about to be deleted!', E_USER_WARNING);
  459.             $retval .= $this->_back_link();
  460.             return $retval;
  461.         }
  462.  
  463.         // Verify "delopt" exists unless it is zero
  464.         // 0 => use "tid" for a dummy lookup.
  465.         $v = $this->_delopt;
  466.         if (!$v{
  467.             $v = $this->_tid;
  468.         }
  469.         $tname = $this->topic_verify($v);
  470.         if ($tname === FALSE{
  471.             $retval .= $this->_back_link();
  472.             return $retval;
  473.         }
  474.  
  475.         if ($this->_delopt{
  476.             $query = "UPDATE {$this->_prefix}entries SET tid=? WHERE tid=?";
  477.             $params = array('delopt' => $this->_delopt'tid' => $this->_tid);
  478.             $retval .= "<p>Moving entries to $tname</p>\n";
  479.         } else {
  480.             $query = "DELETE FROM {$this->_prefix}entries WHERE tid=?";
  481.             $params = array('tid' => $this->_tid);
  482.             $retval .= "<p>Deleting any existing entries for $tname</p>\n";
  483.         }
  484.         
  485.         $numrows = $this->_db->change($query$params);
  486.  
  487.         if ($numrows == -1{
  488.             trigger_error('No topic found to delete', E_USER_WARNING);
  489.             $retval .= $this->_back_link();
  490.             return $retval;        
  491.         }
  492.         
  493.         $numrows = $this->_db->change("DELETE FROM {$this->_prefix}topics WHERE tid=?", array('tid' => $this->_tid));
  494.  
  495.         if ($numrows == 1{
  496.             $retval .= "<p><b>Topic deleted</b></p>\n";
  497.         } else {
  498.             trigger_error('No topic found to delete', E_USER_WARNING);
  499.         }
  500.  
  501.         $retval .= $this->_back_link();
  502.  
  503.         return $retval;
  504.     }
  505.  
  506.     /**
  507.      * Return HTML for hidden fields to maintain current cgi variable values through html form
  508.      *
  509.      * @param array field names
  510.      * @return string HTML with hidden fields (<input type="hidden">)
  511.      */
  512.     private function print_form_vars($cgi_names) {
  513.         
  514.         $retval = '';
  515.         
  516.         foreach ($cgi_names as $varname) {
  517.             $private_varname = '_' . $varname;
  518.             if (isset($this->$private_varname)) {
  519.                 if (is_array($this->$private_varname)) {
  520.                     foreach ($this->$private_varname as $key => $value)
  521.                         $retval .= "<input type=\"hidden\" name=\"wl_{$varname}[$key]\" value=\"" . htmlspecialchars($value) . "\" />\n";
  522.                 } else {
  523.                     $retval .= "<input type=\"hidden\" name=\"wl_{$varname}\" value=\"" . htmlspecialchars($this->$private_varname"\" />\n";
  524.                 }
  525.             }
  526.         }
  527.         
  528.         return $retval;
  529.     }
  530.  
  531.     /**
  532.      * Edit topics. wl_mode=edit+topics
  533.      *
  534.      * @return string HTML form for editing topics
  535.      */
  536.     private function topic_edit() {
  537.         
  538.         $retval = "<h2>Edit topics</h2>\n";
  539.  
  540.         if (! $this->_admin{
  541.             trigger_error('Not logged in', E_USER_WARNING);
  542.             return $retval;
  543.         }
  544.  
  545.         $tsel = $this->topic_selectbox('delopt'-1'Delete');
  546.         if (!$tsel)
  547.             return $retval;
  548.         $resultset $this->_db->fetch("SELECT * FROM {$this->_prefix}topics");
  549.         if (empty($resultset))
  550.             return $retval;
  551.         $retval .= "<p><form action=\"{$this->_self}\" method=\"post\">\n";
  552.         $retval .= $this->print_form_vars(array('offset''topic''search'));
  553.         $retval .= "<table border=\"1\" cellpadding=\"3\">\n";
  554.         $retval .= '<tr><th>Id</th><th>Name</th>';
  555.         $retval .= "<th>Icon</th><th>&nbsp;</th></tr>\n";
  556.         foreach ($resultset as $array{
  557.             $retval .= '<tr>';
  558.             $retval .= "<td>{$array['tid']}</td>\n";
  559.             $retval .= "<td>{$array['name']}</td>\n";
  560.             $retval .= "<td><img src=\"{$array['icon']}\"> <a href=\"{$array['icon']}\" />{$array['icon']}</a></td>\n";
  561.             $retval .= "<td><input type=\"radio\" name=\"wl_tid\" value=\"{$array['tid']}\" /></td>";
  562.             $retval .= "</tr>\n";
  563.         }
  564.         $retval .= "<td colspan=\"3\" align=\"right\">Select none</td>\n";
  565.         $retval .= "<td><input type=\"radio\" name=\"wl_tid\" value=\"\" checked /></td>";
  566.         $retval .= "</table>\n";
  567.         $retval .= "<table><tr>\n";
  568.         $retval .= "<td>Name:</td><td><input type=\"text\" name=\"wl_name\" maxlength=\"80\" /></td>\n";
  569.         $retval .= "</tr><tr>\n";
  570.         $retval .= "<td>Icon:</td><td><input type=\"text\" name=\"wl_icon\" maxlength=\"128\" /></td>\n";
  571.         $retval .= "</tr></table>\n";
  572.         $retval .= "<input type=\"submit\" name=\"wl_mode\" value=\"change topic\" />\n";
  573.         $retval .= "<input type=\"submit\" name=\"wl_mode\" value=\"add topic\" /><br />\n";
  574.         $retval .= 'Move or delete entries currently in topic: ';
  575.         $retval .= $tsel;
  576.         $retval .= "<input type=\"submit\" name=\"wl_mode\" value=\"delete topic\" />\n";
  577.         $retval .= "</form></p>\n";
  578.         $retval .= $this->_back_link();
  579.  
  580.         return $retval;
  581.     }
  582.  
  583.     /**
  584.      * Save weblog options (data from 'vars' table). wl_mode=save+setup
  585.      *
  586.      * @return string HTML with success or failure message
  587.      */
  588.      private function setup_save() {
  589.         
  590.         $retval = "<h2>Updating configuration</h2>\n";
  591.  
  592.         if (! $this->_admin{
  593.             trigger_error('Not logged in', E_USER_WARNING);
  594.             return $retval;
  595.         }
  596.  
  597.         foreach ($this->_value as $k => $v{
  598.             $this->setvar($k$v);
  599.         }
  600.  
  601.         $retval .= "<p><b>Configuration saved</b></p>\n";
  602.         $retval .= $this->_back_link();
  603.         
  604.         return $retval;
  605.     }
  606.  
  607.     /**
  608.      * Edit weblog options (data from 'vars' table). wl_mode=edit+setup
  609.      *
  610.      * @return string HTML form for editing weblog options
  611.      */
  612.     private function setup_edit() {
  613.         
  614.         $retval =  "<h2>Edit weblog setup</h2>\n";
  615.  
  616.         if (! $this->_admin{
  617.             trigger_error('Not logged in', E_USER_WARNING);
  618.             return $retval;
  619.         }
  620.  
  621.         $resultset = $this->_db->fetch("SELECT * FROM {$this->_prefix}vars WHERE editable <> ? ORDER BY name ASC", array('editable' => 'no'));
  622.         if (empty($resultset))
  623.             return $retval;
  624.  
  625.         $retval .= "<p><form action=\"{$this->_self}\" method=\"post\">\n";
  626.         $retval .= $this->print_form_vars(array('offset''topic''search'));
  627.         foreach ($resultset as $array{
  628.             
  629.             if ($array['name'] == 'password') {
  630.                 $retval .= "<h2>new {$array['name']} (leave empty for unchanged)</h2>\n";
  631.                 $retval .= '<p>';
  632.                 $retval .= "<input type=\"text\" name=\"wl_value[{$array['name']}]\" value=\"\" size=\"60\" />\n";
  633.                 $retval .= '</p>';
  634.                 continue;
  635.             }
  636.  
  637.             $retval .= "<h2>{$array['name']}</h2>\n";
  638.             if ($array['help'])
  639.                 $retval .= "<p>{$array['help']}</p>\n";
  640.             $retval .= '<p>';
  641.             if ($array['editable'] == 'single') {
  642.                 $retval .= "<input type=\"text\" name=\"wl_value[{$array['name']}]\" value=\"" . htmlspecialchars($array['value']) . "\" size=\"60\" />\n";
  643.             } else {
  644.                 $retval .= "<textarea name=\"wl_value[{$array['name']}]\" rows=\"5\" cols=\"60\" wrap=\"virtual\">" . htmlspecialchars($array['value']) . "</textarea>\n";
  645.             }
  646.             $retval .= '</p>';
  647.         }
  648.         $retval .= "<p><input type=\"submit\" name=\"wl_mode\" value=\"save setup\" /></p>\n";
  649.         $retval .= "</form></p>\n";
  650.         $retval .= $this->_back_link();
  651.         
  652.         return $retval;
  653.     }
  654.  
  655.     /**
  656.     * Load a weblog entry overriding current title, tid, body, more values
  657.     *
  658.     * @param int entry id
  659.     * @return bool TRUE if entry found, FALSE otherwise
  660.     * @access private
  661.     */
  662.     private function entry_load($eid) {
  663.         
  664.         $resultset = $this->_db->fetch("SELECT * FROM {$this->_prefix}entries WHERE eid=?", array('eid' => $eid));
  665.  
  666.         if (empty($resultset)) {
  667.             trigger_error('The entry was not found', E_USER_ERROR);
  668.             return FALSE;
  669.         }
  670.  
  671.         $entry = $resultset[0];
  672.         
  673.         foreach (array('title', 'tid', 'body', 'more') as $key) {
  674.             $private_varname = '_' . $key;
  675.             // if value is already loaded from a form,
  676.             // don't overwrite with value from the database
  677.             // otherwise edit -> preview -> edit does not work
  678.             if (! isset($this->$private_varname)) {
  679.                 $this->$private_varname $entry[$key];
  680.             }
  681.         }
  682.  
  683.         return TRUE;
  684.     }
  685.  
  686.     /**
  687.      * Edit an entry. wl_mode=edit+entry
  688.      *
  689.      * Uses properties:  eid, tid, title, body, more, update
  690.      * The entry can be given from CGI variables, start blank, or be loaded from the DB.
  691.      * @return string HTML with form for editing entry
  692.      */
  693.     private function entry_edit() {
  694.  
  695.         $retval = "<h2>Edit entry</h2>\n";
  696.  
  697.         if (! $this->_admin{
  698.             trigger_error('Not logged in', E_USER_WARNING);
  699.             return $retval;
  700.         }
  701.                 
  702.         if (isset($this->_eid)) {
  703.             if (!$this->entry_load($this->_eid)) {
  704.                 $retval .= $this->_back_link();
  705.                 return $retval;
  706.             }
  707.         }
  708.         
  709.         $tsel = $this->topic_selectbox('tid'$this->_tid);
  710.         if (!$tsel{
  711.             return $retval;
  712.         }
  713.  
  714.         $retval .= "<p><form action=\"{$this->_self}\" method=\"post\">\n";
  715.         $retval .= $this->print_form_vars(array('offset''topic''search'));
  716.         if (isset($this->_eid)) {
  717.             $retval .= "<input type=\"hidden\" name=\"wl_eid\" value=\"{$this->_eid}\" />\n";
  718.         }
  719.         $retval .= "Title: <input type=\"text\" name=\"wl_title\" value=\"" . htmlspecialchars($this->_title"\" maxlength=\"200\" size=\"60\" /><br />\n";
  720.         $retval .= "Topic:&nbsp;";
  721.         $retval .= $tsel;
  722.         $retval .= "<br />\n";
  723.         $retval .= "Body:<br />\n";
  724.         $retval .= "<textarea name=\"wl_body\" rows=\"10\" cols=\"60\" wrap=\"virtual\">" htmlspecialchars($this->_body"</textarea><br />\n";
  725.         $retval .= "More:<br />\n";
  726.         $retval .= "<textarea name=\"wl_more\" rows=\"10\" cols=\"60\" wrap=\"virtual\">" htmlspecialchars($this->_more"</textarea><br />\n";
  727.         // Only offer "update" when editing an existing entry
  728.         if (isset($this->_eid)) {
  729.             $retval .= "<input type=\"checkbox\" name=\"wl_update\" value=\"yes\"";
  730.             if ($this->_update == 'yes'{
  731.                 $retval .= ' checked';
  732.             }
  733.             $retval .= " /> Update timestamp<br />\n";
  734.         }
  735.         $retval .= "<input type=\"submit\" name=\"wl_mode\" value=\"preview entry\" />\n";
  736.         $retval .= "<input type=\"submit\" name=\"wl_mode\" value=\"save entry\" /><br />\n";
  737.         $retval .= "</form></p>\n";
  738.         $retval .= $this->_back_link();
  739.         
  740.         return $retval;
  741.     }
  742.  
  743.     /**
  744.      * Preview an entry. wl_mode=preview+entry
  745.      *
  746.      * Uses properties:  eid, tid, title, body, more, update
  747.      * @return string HTML with preview of entry
  748.      */
  749.     private function entry_preview() {
  750.         
  751.         $retval = "<h2>Preview entry</h2>\n";
  752.  
  753.         if (! $this->_admin{
  754.             trigger_error('Not logged in', E_USER_WARNING);
  755.             return $retval;
  756.         }
  757.  
  758.         if ($this->_eid{
  759.             // existing entry
  760.             $resultset = $this->_db->fetch("SELECT created,updated,name,icon FROM {$this->_prefix}entries,{$this->_prefix}topics WHERE eid=? AND {$this->_prefix}topics.tid=?", array('eid' => $this->_eid'topics_tid' => $this->_tid));
  761.         } else {
  762.             // new entry
  763.             $resultset = $this->_db->fetch("SELECT name,icon FROM {$this->_prefix}topics WHERE tid=?", array('tid' => $this->_tid));
  764.         }
  765.         if (empty($resultset)) {
  766.             return $retval;
  767.         }
  768.         $array = $resultset[0];
  769.  
  770.         // New entry? Set times. Otherwise check for update.
  771.         $tm = time();
  772.         if (! array_key_exists('created', $array)) {
  773.             $array['created'] = $tm;
  774.             $array['updated'] = $tm;
  775.         } else if ($this->_update == 'yes'{
  776.             $array['updated'] = $tm;
  777.         }
  778.  
  779.         // Set remaining vars
  780.         foreach (array('eid', 'tid', 'title', 'body', 'more') as $varname) {
  781.             $private_varname = '_' . $varname;
  782.             $array[$varname] = $this->$private_varname;
  783.         }
  784.  
  785.         $retval .= $this->subst_tokens($this->getvar('more')$arrayfalse"\n";
  786.  
  787.         $retval .= "<p><form action=\"{$this->_self}\" method=\"post\">\n";
  788.         $retval .= $this->print_form_vars(array('offset''topic''search'));
  789.         foreach (array('eid''tid''title''body''more''update'as $varname{
  790.             $private_varname = '_' . $varname;
  791.             if (isset($this->$private_varname)) {
  792.                 $retval .= "<input type=\"hidden\" name=\"wl_{$varname}\" value=\"" . htmlspecialchars($this->$private_varname"\" />\n";
  793.             }
  794.         }
  795.         $retval .= "<input type=\"submit\" name=\"wl_mode\" value=\"edit entry\" />\n";
  796.         $retval .= "<input type=\"submit\" name=\"wl_mode\" value=\"save entry\" />\n";
  797.         $retval .= "</form></p>\n";
  798.  
  799.         $retval .= $this->_back_link();
  800.         
  801.         return $retval;
  802.     }
  803.  
  804.     /**
  805.      * Save an entry. wl_mode=save+entry
  806.      *
  807.      * Uses properties:  eid, tid, title, body, more, update
  808.      * @return string HTML with success or failure message
  809.      */
  810.     private function entry_save() {
  811.  
  812.         $retval = "<h2>Saving entry</h2>\n";
  813.  
  814.         if (! $this->_admin{
  815.             trigger_error('Not logged in', E_USER_WARNING);
  816.             return $retval;
  817.         }
  818.  
  819.         if ($this->topic_verify($this->_tid=== FALSE{
  820.             $retval .=    $this->_back_link();
  821.             return $retval;
  822.         }
  823.         
  824.         $tm = time();
  825.         if ($this->_eid{
  826.             $query = "UPDATE {$this->_prefix}entries SET title=?, tid=?, body=?, more=?";
  827.             $params = array('title' => $this->_title'tid' => $this->_tid'body' => $this->_body'more' => $this->_more);
  828.             if ($this->_update == 'yes'{
  829.                 $query .= ', updated=?';
  830.                 $params['updated'] = $tm;
  831.             }
  832.             $query .= ' WHERE eid=?';
  833.             $params['eid'] = $this->_eid;
  834.         } else {
  835.             $query = "INSERT INTO {$this->_prefix}entries (titletidcreatedupdatedbodymoreVALUES (?,?,?,?,?,?)";
  836.             $params = array('title' => $this->_title'tid' => $this->_tid'created' => $tm'updated' => $tm'body' => $this->_body'more' => $this->_more);
  837.         }
  838.  
  839.         $numrows = $this->_db->change($query$params);
  840.         
  841.         if ($this->_eid && $numrows == 0{
  842.             $retval .= "<p><b>Entry not changed</b></p>\n";
  843.         } else {
  844.             $retval .= "<p><b>Entry saved</b></p>\n";
  845.         }
  846.  
  847.         $retval .= $this->_back_link();
  848.         
  849.         return $retval;
  850.     }
  851.  
  852.     /**
  853.      * Delete one entry. wl_mode=delete+entry
  854.      *
  855.      * Uses properties: eid
  856.      * @return string HTML with success or failure message
  857.      */
  858.     private function entry_delete() {
  859.         
  860.         $retval = "<h2>Deleting entry</h2>\n";
  861.  
  862.         if (! $this->_admin{
  863.             trigger_error('Not logged in', E_USER_WARNING);
  864.             return $retval;
  865.         }
  866.  
  867.         $numrows = $this->_db->change("DELETE FROM {$this->_prefix}entries WHERE eid=?", array('eid' => $this->_eid));
  868.  
  869.         if ($numrows == 1{
  870.             $retval .= "<p><b>Entry deleted</b></p>\n";
  871.         } else {
  872.             trigger_error('No entry found to delete', E_USER_ERROR);
  873.         }
  874.  
  875.         $retval .= $this->_back_link();
  876.  
  877.         return $retval;
  878.     }
  879.  
  880.     /**
  881.      * Empty search conditions and return all entries. wl_mode=$this->getvar('reset_text')
  882.      *
  883.      * @return string HTML with entries
  884.      */
  885.     private function entry_reset() {
  886.         
  887.         $this->_topic = NULL;
  888.         $this->_search = NULL;
  889.         $this->_offset = 0;
  890.  
  891.         return $this->entry_search();
  892.     }
  893.  
  894.     /**
  895.      * Returns query and array of parameters for selecting entries
  896.      *
  897.      * Returns a select query limiting entries based on CGI variables
  898.      * and topic restriction. Further conditions can be appended.
  899.      * @param int columns to select
  900.      * @return array item 1: query with placeholders item 2: array with parameters
  901.      */
  902.     private function select_query($colspec = '*') {
  903.  
  904.         $query = "SELECT $colspec FROM {$this->_prefix}entries, {$this->_prefix}topics WHERE {$this->_prefix}entries.tid={$this->_prefix}topics.tid";
  905.         $params = array();
  906.  
  907.         if (0 < $this->_topic{
  908.             $query .= " AND {$this->_prefix}topics.tid=?";
  909.             $params['topics_tid'] = intval($this->_topic);
  910.         }
  911.         if (! empty($this->_search)) {
  912.             $keywords = preg_split('/\s+/', $this->_search);
  913.             $index 0;
  914.             foreach ($keywords as $k{
  915.                 $query .= " AND ({$this->_prefix}entries.title LIKE ? OR {$this->_prefix}entries.body LIKE ? OR {$this->_prefix}entries.more LIKE ?)";
  916.                 $params += array("entries_title_$index" => "%$k%", "entries_body_$index" => "%$k%", "entries_more_$index" => "%$k%");
  917.                 $index++;
  918.             }
  919.         }
  920.  
  921.         return array($query, $params);
  922.     }
  923.  
  924.     /**
  925.      * Add ORDER BY and LIMIT to query for selecting entries
  926.      *
  927.      * @return array item 1: query with placeholders item 2: array with parameters
  928.      */
  929.     private function generate_search() {
  930.  
  931.         list($query, $params) = $this->select_query();
  932.         $query .= ' ORDER BY updated DESC';
  933.         
  934.         $off $this->_offset;
  935.         $max $this->getvar('max_articles');
  936.         if ($max{
  937.             if (!$off) {
  938.                 $off = 0;
  939.             }
  940.             // Search for an extra entry. Do not display the extra
  941.             // entry, it is used to determine whether a next icon
  942.             // is needed.
  943.             $query .= " LIMIT $off" . ($max+1);
  944.         }
  945.  
  946.         return array($query, $params);
  947.     }
  948.  
  949.     /**
  950.      * Generates a (partial) list of entries, possibly restricted by topic or searched text. wl_mode=$this->getvar('search_text')
  951.      *
  952.      * Uses properties: topic, offset, search
  953.      * @return string HTML with entries
  954.      */
  955.     private function entry_search() {
  956.         
  957.         $retval = '';
  958.  
  959.         $sep = $this->getvar('separator');
  960.         $max $this->getvar('max_articles');
  961.  
  962.         list($query$params$this->generate_search();                            
  963.         $array $this->_db->fetch($query$params);
  964.         $rows count($array);
  965.  
  966.         $next '';
  967.         if ($max && $rows $max{
  968.             $next = $this->button_offset('next_html'$this->_offset + $max);
  969.             $rows $max;
  970.         }
  971.  
  972.         $prev = '';
  973.         if ($this->_offset > 0{
  974.             $prev = $this->button_offset('prev_html'$this->_offset - $max);
  975.         }
  976.  
  977.         if ($rows == 0) {
  978.             $retval .= "<b>{$this->_dbvars['no_result_string']}</b>\n";
  979.             $retval .= "$sep\n";
  980.         } else {
  981.             $retval .= $this->print_entries('article'$array"$sep\n");
  982.         }
  983.  
  984.         $retval .= $this->print_control($prev$next);
  985.         
  986.         return $retval;
  987.     }
  988.  
  989.     /**
  990.      * Compact list of all entries (wl_mode=archive)
  991.      *
  992.      * @return string HTML with list of entries
  993.      */
  994.     private function entry_archive() {
  995.         
  996.         $retval = '';
  997.         
  998.         $sep = $this->getvar('separator');
  999.         // Temporarily disable limiting articles
  1000.         $this->_dbvars['max_articles'0;
  1001.  
  1002.         list($query$params$this->generate_search();
  1003.         $array $this->_db->fetch($query$params);
  1004.         $rows count($array);
  1005.  
  1006.         if ($rows == 0{
  1007.             $retval .= "<b>{$this->_dbvars['no_result_string']}</b>\n";
  1008.             $retval .= "$sep\n";
  1009.         } else {
  1010.             $retval .= $this->print_entries('archive'$array"\n");
  1011.         }
  1012.  
  1013.         $retval .= "$sep\n";
  1014.  
  1015.         // no 'previous' or 'next' links needed
  1016.         $retval .= $this->print_control('''');
  1017.         
  1018.         return $retval;
  1019.     }
  1020.  
  1021.     /**
  1022.      * Used to create a link to the previous or next entry
  1023.      *
  1024.      * @param int last updated time of the current entry
  1025.      * @param int -1 or 1 for next or previous entry
  1026.      * @return int entry id of previous or next entry
  1027.      */
  1028.     private function entry_relative($time, $offset) {
  1029.         
  1030.         if ($offset < 0) {
  1031.             $cmp = '>';
  1032.             $dir = 'ASC';
  1033.             $offset = abs($offset);
  1034.         } else if ($offset > 0) {
  1035.             $cmp = '<';
  1036.             $dir = 'DESC';
  1037.         } else {
  1038.             return $id;
  1039.         }
  1040.         $offset--;
  1041.  
  1042.         list($query, $params) = $this->select_query('eid');
  1043.         $query .= " AND updated $cmp ? ORDER BY updated $dir LIMIT $offset,1";
  1044.         $params['updated'] = $time;
  1045.         $resultset = $this->_db->fetch($query$params);
  1046.  
  1047.         if (empty($resultset))
  1048.             return;
  1049.             
  1050.         $array $resultset[0];
  1051.         
  1052.         return $array['eid'];
  1053.     }
  1054.  
  1055.     /**
  1056.      * Display one complete entry (wl_mode=more). 
  1057.      *
  1058.      * Uses property: eid (current entry id number)
  1059.      * @return string HTML for entry display
  1060.      */
  1061.     private function entry_display() {
  1062.  
  1063.         $retval = '';
  1064.         $prev = '';
  1065.         $next = '';
  1066.  
  1067.     if (is_null($this->_eid)) {
  1068.             trigger_error('Missing entry ID', E_USER_WARNING);
  1069.             $retval .= $this->print_control(falsefalse);
  1070.             return $retval;
  1071.         }
  1072.         list($query, $params) =  $this->select_query();
  1073.         $query .=  ' AND eid=?';
  1074.         $params['eid'$this->_eid;
  1075.  
  1076.         $resultset $this->_db->fetch($query$params);
  1077.  
  1078.         if (empty($resultset)) {
  1079.             $retval .= "<b>No entry found.</b>\n";
  1080.         } else {
  1081.             $mf = $this->getvar('more');
  1082.             $array $resultset[0];
  1083.             $retval .= $this->subst_tokens($mf$array"\n";
  1084.             $id $this->entry_relative($array['updated']-1);
  1085.             if ($id{
  1086.                 $prev .= $this->button_cycle('prev_html'$id);
  1087.             }
  1088.             $id = $this->entry_relative($array['updated']1);
  1089.             if ($id{
  1090.                 $next .= $this->button_cycle('next_html'$id);
  1091.             }
  1092.         }
  1093.         $retval .= $this->getvar('separator'"\n";
  1094.  
  1095.         $retval .= $this->print_control($prev$next);
  1096.  
  1097.         return $retval;
  1098.     }
  1099.  
  1100.     /**
  1101.      * Print per entry admin menu maintaining search parameters
  1102.      *
  1103.      * @param int entry id
  1104.      * @return string HTML with admin menu (edit/delete links)
  1105.      */
  1106.     private function entry_print_admin($eid) {
  1107.         
  1108.         $query_string = $this->_query_string(array('offset' => $this->_offset,
  1109.                                          'topic' => $this->_topic,
  1110.                                          'search' => $this->_search));
  1111.         if ($query_string{
  1112.             $query_string = '&' . $query_string;
  1113.         }
  1114.         $query_string = htmlspecialchars($query_string);
  1115.         $adm = '<p>';
  1116.         $adm .= "<a href=\"{$this->_self}?wl_mode=edit+entry&amp;wl_eid=$eid$query_string\">[edit]</a>\n";
  1117.         $adm .= "<a href=\"{$this->_self}?wl_mode=delete+entry&amp;wl_eid=$eid$query_string\">[delete]</a></p>\n";
  1118.  
  1119.         return $adm;
  1120.     }
  1121.  
  1122.     /**
  1123.      * Clean up GET/POST contents
  1124.      *
  1125.      * Ensure all non string CGI variables are clean
  1126.      * Strings are always escaped for db use by using prepared statements
  1127.      * name, icon, title, body, more, value: used in db searches
  1128.      * mode, password, update: not used in db searches
  1129.      */
  1130.     private function sanitize_input() {
  1131.         
  1132.         // First strip slashes if necessary, escaping is
  1133.         // handled by using prepared statements
  1134.         if (get_magic_quotes_gpc()) {
  1135.             foreach ($this->_string_cgi as $varname{
  1136.                 $private_varname = '_' . $varname;
  1137.                 if (isset($this->$private_varname)) {
  1138.                     $this->$private_varname stripslashes($this->$private_varname);
  1139.                 }
  1140.             }
  1141.         }
  1142.         // Ensure numeric CGI vars are valid.
  1143.         foreach ($this->_numeric_cgi as $varname{
  1144.             $private_varname = '_' . $varname;
  1145.             if (isset($this->$private_varname)) {
  1146.                 $this->$private_varname intval($this->$private_varname);
  1147.             }
  1148.         }
  1149.         // Sanitize. shrink spaces, remove prefix/suffix spaces.
  1150.         if (isset($this->_search)) {
  1151.             $this->_search = preg_replace('/ +/'' '$this->_search);
  1152.             $this->_search = trim($this->_search);
  1153.             // Only keep first 3 keywords
  1154.             // Do this here so the search box contains the updated search string.
  1155.             if (preg_match('/([^ ]+ [^ ]+ [^ ]+) .*/'$this->_search$reg)) {
  1156.                 $this->_search = $reg[1];
  1157.             }
  1158.         }
  1159.         // md5 hash of password is saved in database
  1160.         if (isset($this->_password)) {
  1161.             $this->_password = md5($this->_password);
  1162.         }    
  1163.     }
  1164.  
  1165.     /**
  1166.      * Format found entries
  1167.      *
  1168.      * @param string template in which to replace tokens
  1169.      * @param array found entries
  1170.      * @param string HTML to separate entries
  1171.      * @return string HTML with formatted entries
  1172.      */
  1173.     private function print_entries($format, $array, $separator) {
  1174.         
  1175.         $retval = '';
  1176.         
  1177.         $template = $this->getvar($format);
  1178.  
  1179.         for ($i 0$rows count($array)$i $rows$i++{
  1180.             $entry = $array[$i];
  1181.             $retval .= $this->subst_tokens($template$entry"\n";
  1182.             $retval .= $separator;
  1183.         }
  1184.         
  1185.         return $retval;
  1186.     }
  1187.     
  1188.  
  1189.     /**
  1190.      * Substitute an entry into a template via tokens
  1191.      *
  1192.      * @param string template in which to replace tokens
  1193.      * @param array content and metadata of the entry 
  1194.      * @param bool whether to print edit/delete links or not 
  1195.      * @return array string HTML to be displayed
  1196.      */
  1197.     private function subst_tokens($format, $entry, $allow_admin = TRUE) {
  1198.  
  1199.         // Create morelink for @MORE@
  1200.         if ($entry['more']) {
  1201.             $ml = $this->getvar('morelink');
  1202.             $query_string $this->_query_string(array('offset' => $this->_offset,
  1203.                                          'topic' => $this->_topic,
  1204.                                          'search' => $this->_search));
  1205.             if ($query_string{
  1206.                 $query_string = '&' . $query_string;
  1207.             }
  1208.             $query_string = htmlspecialchars("{$this->_self}?wl_mode=more&wl_eid={$entry['eid']}$query_string");
  1209.             $ml = str_replace('@URL@', $query_string, $ml);
  1210.         } else {
  1211.             $ml = '';
  1212.         }
  1213.  
  1214.         if ($entry['created'] != $entry['updated']) {
  1215.             $upd = $this->getvar('update_text');
  1216.             $upd str_replace('@DATE@'date($this->getvar('updated_date')$entry['updated'])$upd);
  1217.         } else {
  1218.             $upd = '';
  1219.         }
  1220.  
  1221.         // Add edit/delete links or not (not if no admin or when previewing)
  1222.         if ($this->_admin && $allow_admin{
  1223.             $al = $this->entry_print_admin($entry['eid']);
  1224.         } else {
  1225.             $al = '';
  1226.         }
  1227.  
  1228.         $trans = array(
  1229.             '@TITLE@' => $entry['title'],
  1230.             '@CREATED@' => date($this->getvar('created_date')$entry['created']),
  1231.             '@UPDATED@' => $upd,
  1232.             '@ICON@' => "<a href=\"{$this->_self}?wl_topic={$entry['tid']}\"><img src=\"{$entry['icon']}\" alt=\"Topic: {$entry['name']}\" " . $this->getvar('topic_icon_attrs'" /></a>",
  1233.             '@TOPIC@' => $entry['name'],
  1234.             '@BODY@' => $entry['body'],
  1235.             '@ID@' => $entry['eid'],
  1236.             '@TOPICID@' => $entry['tid'],
  1237.             '@MORELINK@' => $ml,
  1238.             '@BACKLINK@' => $this->_back_link(),
  1239.             '@SELF@' => $this->_self,
  1240.             '@MORE@' => $entry['more'],
  1241.             '@ADMIN@' => $al);
  1242.  
  1243.         // Substitute entry
  1244.         return strtr($format$trans);
  1245.     }
  1246.  
  1247.     /**
  1248.      * Next/previous links for paged list of entries
  1249.      *
  1250.      * @param string 'next_html' or 'prev_html' configuration variable name
  1251.      * @param int offset in the list of entries
  1252.      * @return string HTML for the next/previous link
  1253.      */
  1254.     private function button_offset($var, $off) {
  1255.         
  1256.         if ($off < 0) {
  1257.             $off = 0;
  1258.         }
  1259.         
  1260.         $cgi_vars = array('search' => $this->_search,
  1261.                           'topic' => $this->_topic,
  1262.                           'offset' => $off);
  1263.         $text $this->getvar($var);
  1264.         
  1265.         return $this->_href_link($cgi_vars$text);
  1266.     }
  1267.  
  1268.     /**
  1269.      * Next/previous links for complete entries (wl_mode=more)
  1270.      *
  1271.      * @param string 'next_html' or 'prev_html' configuration variable name
  1272.      * @param int id number of previous/next weblog entry
  1273.      * @return string HTML for the next/previous link
  1274.      */
  1275.     private function button_cycle($var, $id) {
  1276.         
  1277.         $cgi_vars = array(    'mode' => 'more',
  1278.                             'eid' => $id,
  1279.                             'search' => $this->_search,
  1280.                             'topic' => $this->_topic,
  1281.                             'offset' => $this->_offset);
  1282.         $text $this->getvar($var);
  1283.         
  1284.         return $this->_href_link($cgi_vars$text);
  1285.     }
  1286.  
  1287.     /**
  1288.      * Show search box, topic select, ...
  1289.      *
  1290.      * @param array int string bool obj omschrijving
  1291.      * @param array int string bool obj omschrijving
  1292.      * @return string omschrijving
  1293.      */
  1294.     private function print_control($prev, $next) {
  1295.         
  1296.         if ($prev == '')
  1297.             $prev = $this->getvar('blank_html');
  1298.         if ($next == '')
  1299.             $next $this->getvar('blank_html');
  1300.         $ts $this->topic_selectbox('topic'$this->_topic'all');
  1301.         if (!$ts)
  1302.             return;
  1303.         $admin '';
  1304.         if ($this->_admin{
  1305.             $query_string = $this->_query_string(array('offset' => $this->_offset,
  1306.                                          'topic' => $this->_topic,
  1307.                                          'search' => $this->_search));
  1308.             if ($query_string)
  1309.                 $query_string '&' $query_string;
  1310.             $query_string htmlspecialchars($query_string);
  1311.             $admin .= '<p>';
  1312.             $admin .= "<a href=\"{$this->_self}?wl_mode=edit+entry$query_string\">[add entry]</a>\n";
  1313.             $admin .= "<a href=\"{$this->_self}?wl_mode=edit+topics$query_string\">[edit topics]</a>\n";
  1314.             $admin .= "<a href=\"{$this->_self}?wl_mode=edit+setup$query_string\">[edit setup]</a>\n";
  1315.             $admin .= "<a href=\"{$this->_self}?wl_mode=logout$query_string\">[log out]</a></p>\n";
  1316.         }
  1317.  
  1318.         $trans = array(    '@PREV_HTML@' => $prev,
  1319.                 '@SELF@' => $this->_self,
  1320.                 '@TOPIC_BOX@' => $ts,
  1321.                 '@KEYWORDS@' => htmlspecialchars($this->_search),
  1322.                 '@SEARCH_TEXT@' => $this->getvar('search_text'),
  1323.                 '@RESET_TEXT@' => $this->getvar('reset_text'),
  1324.                 '@NEXT_HTML@' => $next,
  1325.                 '@ADMIN@' => $admin);
  1326.  
  1327.         return strtr($this->getvar('control')$trans);
  1328.     }
  1329.     
  1330.     /**
  1331.      * Error handling
  1332.      *
  1333.      * Does not work if not defined as public, never called explicitly though
  1334.      * @param int error code
  1335.      * @param string error string
  1336.      * @param string filename where error occured
  1337.      * @param int line number where error occured 
  1338.      */
  1339.     public function handle_error($errno, $err, $file, $line) {
  1340.  
  1341.         $file = basename($file);
  1342.         
  1343.         switch ($errno) {
  1344.             case E_USER_ERROR:
  1345.                 $errorstring =  '<b style="color:red;">ERROR</b> ' .  $err;
  1346.                 if (MINIMALWEBLOG_DEBUG > 0) {
  1347.                     $errorstring .=  " ($fileline $line)";
  1348.                     if (MINIMALWEBLOG_DEBUG > 1) {
  1349.                         $errorstring .= $this->_get_backtrace();
  1350.                     }
  1351.                 }
  1352.                 $this->_errors[$errorstring;
  1353.                 break;
  1354.             case E_USER_WARNING:
  1355.                 $errorstring "<b>WARNING</b$err";
  1356.                 if (MINIMALWEBLOG_DEBUG > 0) {
  1357.                     $errorstring .=  " ($fileline $line)";
  1358.                     if (MINIMALWEBLOG_DEBUG > 1) {
  1359.                         $errorstring .= $this->_get_backtrace();
  1360.                     }
  1361.                 }
  1362.                 $this->_errors[$errorstring;
  1363.                 break;
  1364.             case E_WARNING:
  1365.                 if (MINIMALWEBLOG_DEBUG 0{
  1366.                     $errorstring = '<b>WARNING</b> ' .  $err . " ($fileline $line)";
  1367.                     if (MINIMALWEBLOG_DEBUG > 1) {
  1368.                         $errorstring .= $this->_get_backtrace();
  1369.                     }
  1370.                     $this->_errors[$errorstring;
  1371.                 }
  1372.                 break;
  1373.             case E_NOTICE:
  1374.                 if (MINIMALWEBLOG_DEBUG > 0) {
  1375.                     $this->_errors['<b>NOTICE</b> ' .  $err " ($fileline $line)";
  1376.                 }
  1377.                 break;
  1378.             default:
  1379.                 return;                
  1380.         }
  1381.     }
  1382.     
  1383.     /**
  1384.      * Provides backtrace when MINIMALWEBLOG_DEBUG == 2
  1385.      *
  1386.      * @return string backtrace
  1387.      */
  1388.     private function _get_backtrace() {
  1389.                         
  1390.         ob_start();
  1391.         debug_print_backtrace();
  1392.         $backtrace = '<pre>' . ob_get_contents() . '</pre>';
  1393.         ob_end_clean();
  1394.         
  1395.         return $backtrace;
  1396.     }
  1397.  
  1398.     /**
  1399.      * Verify the topic exists
  1400.      *
  1401.      * @param int topic id
  1402.      * @return mixed name of the topic or FALSE if not exists
  1403.      */
  1404.     private function topic_verify($tid) {
  1405.         
  1406.         if (!$tid) {
  1407.             return FALSE;
  1408.         }
  1409.  
  1410.         $query = "SELECT tidname FROM {$this->_prefix}topics WHERE tid=?";
  1411.         $params = array('tid' => $tid);
  1412.         $resultset = $this->_db->fetch($query$params);
  1413.  
  1414.         if (empty($resultset)) {
  1415.             trigger_error('Topic not found', E_USER_WARNING);
  1416.             return FALSE;
  1417.         } else {
  1418.             return $resultset[0]['name'];
  1419.         }
  1420.     }
  1421.  
  1422.     /**
  1423.      * Configuraton variables are assumed to exist and are required to run
  1424.      *
  1425.      * @param string variable name
  1426.      */
  1427.     private function getvar($name) {
  1428.         
  1429.         if (array_key_exists($name, $this->_dbvars)) {
  1430.             return $this->_dbvars[$name];
  1431.         } else {
  1432.             trigger_error("The configuration variable $name was not foundYou may need to insert this variable into the 'varstable by using the data found in 'weblog.sql'.", E_USER_WARNING);
  1433.         }
  1434.     }
  1435.  
  1436.     /**
  1437.      * Update a weblog (configuration) variable (in class and database)
  1438.      *
  1439.      * @param string variable name
  1440.      * @param string value
  1441.      */
  1442.     private function setvar($name, $value) {
  1443.  
  1444.         if ($name == 'password') {
  1445.             if ($value == '') {
  1446.                 return;
  1447.             } else {
  1448.                 $value = md5($value);
  1449.             }
  1450.         }
  1451.  
  1452.         // No update needed
  1453.         if ($this->getvar($name== $value)
  1454.             return TRUE;
  1455.  
  1456.         $this->_dbvars[$name$value;
  1457.         
  1458.         $query "UPDATE {$this->_prefix}vars SET value=? WHERE name=?";
  1459.         $params = array('value' => $value, 'name' => $name);
  1460.         $numrows = $this->_db->change($query$params);
  1461.  
  1462.         if ($numrows != 1{
  1463.             trigger_error("Cannot set $name/$value", E_USER_ERROR);
  1464.         }
  1465.     }
  1466.     
  1467.     /**
  1468.      * Returns the form HTML necessary to select between topics
  1469.      *
  1470.      * @param string desired variable name to be returned (prefixed with "wl_")
  1471.      * @param int selected topic id or -1 if none
  1472.      * @param string extra (first) option, if any
  1473.      * @return string HTML for the select menu
  1474.      */
  1475.     private function topic_selectbox($cgi, $topic_id, $field = '') {
  1476.  
  1477.         $resultset = $this->_db->fetch("SELECT tid,name FROM {$this->_prefix}topics ORDER BY name");
  1478.         if (empty($resultset))
  1479.             return '';
  1480.  
  1481.         $html = "<select name=\"wl_$cgi\">\n";
  1482.         if ($field)
  1483.             $html .= "<option value=\"0\">$field</option>\n";
  1484.         foreach ($resultset as $array) {
  1485.             $html .= "<option value=\"{$array['tid']}\"";
  1486.             if ($topic_id == $array['tid'])
  1487.                 $html .= " selected=\"selected\"";
  1488.             $html .= ">{$array['name']}</option>\n";
  1489.         }
  1490.         $html .= '</select>';
  1491.  
  1492.         return $html;
  1493.     }
  1494.  
  1495.     /**
  1496.      * Generate RSS file with the current search parameters
  1497.      *
  1498.      * CGI mode: "rss" (wl_mode=rss)
  1499.      * Uses: topic, offset, search
  1500.      */
  1501.     private function rss_print() {
  1502.         
  1503.         // Ignore RSS if it is not configured
  1504.         if (!$this->getvar('rss_title'))
  1505.             return;
  1506.  
  1507.         // If $_SERVER['SERVER_NAME'] does not yield the correct external
  1508.         // hostname in your setup, define MINIMALWEBLOG_EXTERNAL_HOSTNAME with
  1509.         // the correct value. See config.inc.php.
  1510.         if (MINIMALWEBLOG_EXTERNAL_HOSTNAME != ''{
  1511.             $self = 'http://' . MINIMALWEBLOG_EXTERNAL_HOSTNAME . $this->_self;
  1512.         } else {
  1513.             $self = 'http://' . $_SERVER['SERVER_NAME'] . $this->_self;
  1514.         }
  1515.  
  1516.         list($query, $params) = $this->generate_search();
  1517.         $resultset $this->_db->fetch($query$params);
  1518.  
  1519.         header('Content-type: text/xml');
  1520.  
  1521.         echo "<?xml version=\"1.0\"?>\n";
  1522.         echo "<!DOCTYPE rss PUBLIC \"-//Netscape Communications//DTD RSS 0.91//EN\" \"http://my.netscape.com/publish/formats/rss-0.91.dtd\">\n";
  1523.         echo "<rss version=\"0.91\">\n";
  1524.         echo "<channel>\n";
  1525.         echo '<title>' htmlspecialchars($this->getvar('rss_title')) .
  1526.              "</title>\n";
  1527.         echo '<link>' htmlspecialchars($self"</link>\n";
  1528.         echo '<description>' $this->getvar('rss_description'.
  1529.             "</description>\n";
  1530.         echo "<language>nl</language>\n";
  1531.  
  1532.         foreach($resultset as $array{
  1533.             echo '<item><title>' . htmlspecialchars(strip_tags($array['title'])) . "</title>\n";
  1534.             echo '<link>' . htmlspecialchars("$self?wl_mode=more&wl_eid={$array['eid']}") . "</link>\n";
  1535.             echo '<description>' . htmlspecialchars($array['body']) . "</description></item>\n";
  1536.         }
  1537.  
  1538.         echo "</channel></rss>\n";
  1539.     }
  1540.  
  1541.     /**
  1542.      * Cache DB vars to prevent many queries
  1543.      *
  1544.      */
  1545.     private function load_db_vars() {
  1546.         
  1547.         $resultset = $this->_db->fetch("SELECT name,value FROM {$this->_prefix}vars");
  1548.  
  1549.         if (empty($resultset)) {
  1550.             trigger_error("No configuration data found. Ensure the 'vars' table and been created and filled with data from the 'weblog.sql' data file.", E_USER_ERROR);
  1551.             return;
  1552.         }
  1553.         
  1554.         foreach($resultset as $array) {
  1555.             $this->_dbvars[$array['name']] $array['value'];
  1556.         }
  1557.     }
  1558.  
  1559.     /**
  1560.      * Store any GET/POST variables in the class. POST has precedence over GET
  1561.      *
  1562.      * @param array array_merge($_GET, $_POST) 
  1563.      */
  1564.     private function load_cgi_vars($array) {
  1565.         
  1566.         foreach ($array as $key => $value) {
  1567.             if (preg_match('/^wl_(.+)/', $key, $matches) && in_array($matches[1], $this->_valid_cgi)) {
  1568.                 $private_varname = '_' . $matches[1];
  1569.                 $this->$private_varname $value;                
  1570.             }
  1571.         }
  1572.         
  1573.         // Makes for cleaner URLs
  1574.         if ($this->_topic == '0'{
  1575.             $this->_topic = NULL;
  1576.         }
  1577.         if ($this->_search == ''{
  1578.             $this->_search = NULL;
  1579.         }
  1580.     }
  1581.  
  1582.     /**
  1583.      * Login; if already logged in show the main page
  1584.      *
  1585.      * CGI mode: "login" (wl_mode=login)
  1586.      * Trigger error if editing is not allowed from remote host.
  1587.      * See MINIMALWEBLOG_HOSTS_EDITING_ALLOWED constant in config.inc.php
  1588.      */
  1589.     private function login() {
  1590.         
  1591.         if (defined('MINIMALWEBLOG_HOSTS_EDITING_ALLOWED')) {
  1592.             $allowed_hosts = @ unserialize(MINIMALWEBLOG_HOSTS_EDITING_ALLOWED);
  1593.             if (is_array($allowed_hosts)) {
  1594.                 if ((! in_array($_SERVER['REMOTE_HOST'], $allowed_hosts)) && (! in_array($_SERVER['REMOTE_ADDR'], $allowed_hosts)))    {
  1595.                     trigger_error('Editing is not allowed from your location. Check the MINIMALWEBLOG_HOSTS_EDITING_ALLOWED constant definition in config.inc.php', E_USER_ERROR);
  1596.                     return;
  1597.                 }
  1598.             } else {
  1599.                 trigger_error('Configuration error, check the MINIMALWEBLOG_HOSTS_EDITING_ALLOWED constant definition in config.inc.php', E_USER_ERROR);
  1600.                 return;
  1601.             }
  1602.         }
  1603.         
  1604.         if (! $this->_admin{
  1605.             echo $this->_login_form();
  1606.         } else {
  1607.             return $this->entry_search();
  1608.         }
  1609.     }
  1610.  
  1611.     /**
  1612.      * Logout
  1613.      *
  1614.      * Empty and destroy session, set admin property to false
  1615.      * CGI mode: "logout" (wl_mode=logout)
  1616.      */
  1617.     private function logout() {
  1618.         
  1619.         session_start();
  1620.         $_SESSION = array();
  1621.         // empty session cookie if it exists
  1622.         if (isset($_COOKIE[session_name()])) {
  1623.            setcookie(session_name(), '', time() - 42000, '/');
  1624.         }
  1625.         session_destroy();
  1626.         $this->_admin = FALSE;
  1627.         // lose the PHPSESSID request variable if it exists
  1628.         if (isset($_GET[session_name()]|| isset($_POST[session_name()])) {
  1629.             header('Location: http://' . $_SERVER['HTTP_HOST'] . $this->_self);
  1630.         }
  1631.     }
  1632.  
  1633.     /**
  1634.      * Check login status
  1635.      *
  1636.      * @return boolean logged in or not
  1637.      */
  1638.     private function current_login() {
  1639.  
  1640.         // don't start a session for normal users
  1641.         // so those don't get unnecessary cookies set
  1642.         if (! isset($_REQUEST[session_name()])) {
  1643.             return FALSE;
  1644.         }
  1645.         
  1646.         session_start();
  1647.         if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === TRUE) {
  1648.             return TRUE;
  1649.         } else {
  1650.             return FALSE;
  1651.         }
  1652.     }
  1653.     
  1654. }
  1655.  

Documentation generated on Sun, 17 Jun 2007 19:20:45 +0200 by phpDocumentor 1.3.2