root/trunk/common/mvblog_common.php

Revision 779, 37.1 KB (checked in by michiel, 7 months ago)

move captcha stuff to a plugin

Line 
1<?php
2/**
3 * MvBlog -- An open source no-nosense blogtool
4 *
5 * Copyright (C) 2005-2008, Michiel van Baak
6 * Michiel van Baak <mvanbaak@users.sourceforge.net>
7 *
8 * See http://dev.mvblog.org for more information on MvBlog.
9 * That page also provides Bugtrackers, Filereleases etc.
10 *
11 * This program is free software, distributed under the terms of
12 * the GNU General Public License Version 2. See the LICENSE file
13 * at the top of the source tree.
14 *
15 * @package MvBlog
16 * @author Michiel van Baak
17 * @version %%VERSION%%
18 * @copyright 2005-2008 Michiel van Baak
19 */
20
21/**
22 * Class that holds methods that can be used by every part of mvblog
23 * @package MvBlog
24 */
25Class MvBlog_common {
26    /* constants */
27
28    /* variables */
29    /* {{{ */
30    /**
31     * @var object $db The PEAR::DB connected database
32     */
33    public $db;
34    /**
35     * @var string $version The current program version
36     */
37    public $version    = "%%VERSION%%";
38    /**
39     * @var array $plugins Array with registered plugins.
40     * The array looks like this:
41     * <pre>
42     * array (
43     *   [$type] => "name",
44     *   [$type] => "name"
45     * )
46     * </pre>
47     * Where type can be:
48     * - text_output
49     */
50    public $plugins    = array();
51    /**
52     * @var array $authors Array with all active authors.
53     * The array looks like this:
54     * <pre>
55     * array (
56     *   [id] => "fullname",
57     *   [id] => "fullname"
58     * )
59     * </pre>
60     */
61    public $authors    = array();
62    /**
63     * @var array $users Array with all active users.
64     * The array looks like this:
65     * <pre>
66     * array (
67     *   [id] => "fullname",
68     *   [id] => "fullname"
69     * )
70     * </pre>
71     */
72    public $users      = array();
73    /**
74     * @var array $categories Array with all active categories.
75     * The array looks like this:
76     * <pre>
77     * array (
78     *   [id] => array(
79     *     "id" => id,
80     *     "login" => loginname,
81     *     "password" => password,
82     *     "email" => email address,
83     *     "fullname" => userfriendly name,
84     *     "active" => 1 if active, 0 or null otherwise,
85     *     "website" => full url to authors website
86     *   ),
87     *   [id] => ....
88     * )
89     * </pre>
90     */
91    public $categories = array();
92    /**
93     * @var array $dossiers Array with all the dossiers.
94     * The array looks like this:
95     * <pre>
96     * array (
97     *   [id] => array(
98     *      "id" => id,
99     *      "name" => name
100     *   ),
101     *   [id] => ...
102     * )
103     * </pre>
104     */
105    public $dossiers = array();
106    /**
107     * @var array $settings Array with all active settings.
108     * The array looks like this:
109     * <pre>
110     * array (
111     *   [settingname] => "settingvalue",
112     *   [settingname] => "settingvalue"
113     * )
114     * </pre>
115     * Current settingnames:
116     * - blogtitle
117     * - blogdescription
118     * - blogkeywords
119     * - postsperpage
120     * - allowanoncomments
121     * - cleanurl
122     */
123    public $settings   = array();
124    /**
125     * @var array $menuitems The user configured menuitems
126     */
127    public $menuitems  = array();
128    /**
129     * @var string $webroot The webroot for current blog
130     */
131    public $webroot    = "";
132    public $plugman;
133    public $active_plugins = array();
134
135    /* }}} */
136    /* methods */
137    /* __construct {{{ */
138    /**
139     * Setup stuff and handle settings etc and populate the data containers.
140     *
141     * If one of the default values for the function parameters is changed,
142     * please also change the call in mvblog_upgrade constructor.
143     *
144     * @param string $plugindir Directory where the plugins are.
145     * @param int $adminmode Must be 1 if in the admin interface.
146     * @param int $upgrade If set to 1 the data containers will not be populated to allow upgrades to them.
147     */
148    public function __construct($plugindir="plugins/", $adminmode=0, $upgrade=0) {
149        /* start session and output buffering */
150        if (session_id() == "")
151            session_start();
152        ob_start();
153
154        /* handle php bugs with globals overwrite */
155        $this->_handle_php_bugs();
156
157        /* handle magic quotes */
158        $this->_handle_magic_quotes();
159
160        /* get settings from ini file */
161        if (array_key_exists("config", $GLOBALS))
162            $inisettings = $GLOBALS["config"]->getSettings();
163        else
164            $inisettings = array();
165
166        if (!array_key_exists("database", $inisettings))
167            $inisettings["database"] = "";
168
169        /* init database connection */
170        $this->_init_db($inisettings["database"]);
171
172        /* populate the settings array */
173        $this->_get_settings();
174
175        /* set the language etc */
176        putenv("LANG=".$this->lang);
177        setlocale(LC_ALL, $this->lang);
178        $domain = 'messages';
179        if ($adminmode)
180            bindtextdomain($domain, "../locale");
181        else
182            bindtextdomain($domain, "locale");
183        textdomain($domain);
184
185        /* set the timezone (needed for php5.1 and newer) */
186        if (function_exists("date_default_timezone_get") && function_exists("date_default_timezone_set")) {
187            $tz = @date_default_timezone_get();
188            date_default_timezone_set($tz);
189        }
190
191        /* get base href */
192        $this->_get_webroot();
193
194        /* populate the active plugins array */
195        $this->_get_active_plugins();
196
197        if (!$upgrade) {
198            /* populate the authors array */
199            $this->_get_authors();
200
201            /* populate the users array */
202            $this->_get_users();
203
204            /* populate the categories array */
205            $this->_get_categories();
206
207            /* populate the dossiers array */
208            $this->_get_dossiers();
209
210            /* populate the menuitems array */
211            $this->_get_menuitems();
212        }
213
214        /* plugin handling */
215        require_once("plugins.php");
216        $plugman = new MvBlog_pluginmgr($plugindir, $this);
217        $plugman->set_active_plugins($this->active_plugins);
218        $this->plugman = $plugman;
219    }
220    /* }}} */
221    /* data manipulation methods */
222    /* _handle_php_bugs {{{ */
223    /**
224     * Handle some php bugs.
225     *
226     * There's some weird bugs when register_globals is on.
227     * You can clear them with stuff like this: ?GLOBALS&GLOBALS[bla]=test
228     * So what we do is detect this and bail out.
229     * We also make sure that if register_globals is on the gpc stuff will be removed from the globals stuff
230     */
231    protected function _handle_php_bugs() {
232        /**
233         * catch "PHP5 Globals Vulnerability".
234         * code taken from Advisory ttp://www.ush.it/2006/01/25/php5-globals-vulnerability/
235         */
236        if (isset($HTTP_POST_VARS['GLOBALS']) || isset($_POST['GLOBALS']) || isset($HTTP_POST_FILES['GLOBALS']) || isset($_FILES['GLOBALS']) ||
237            isset($HTTP_GET_VARS['GLOBALS']) || isset($_GET['GLOBALS']) || isset($HTTP_COOKIE_VARS['GLOBALS']) || isset($_COOKIE['GLOBALS']))
238            die("GLOBAL GPC hacking attemt!");
239        /**
240         *    if register_globals is on, you cannot turn it off with ini_set.
241         *    The vars will be registered before the ini_set is executed.
242         *    We can fake register_globals is off by removing the GPCFR keys from
243         *    the global var space :) I got the idea from Alan Hogan with his comment on php.net ini_set function docs.
244         *    I rewrote it to match mvblog codestyle
245         */
246        if (ini_get("register_globals")) {
247            foreach ($_GET as $key => $value)
248                if (preg_match("/^([a-z]|_){1}([a-z0-9]|_)*$/si", $key))
249                    unset($GLOBALS[$key]);
250
251            foreach ($_POST as $key => $value)
252                if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
253                    unset($GLOBALS[$key]);
254
255            foreach ($_COOKIE as $key => $value)
256                if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
257                    unset($GLOBALS[$key]);
258
259            foreach ($_FILES as $key => $value)
260                if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
261                    unset($GLOBALS[$key]);
262
263            foreach ($_REQUEST as $key => $value)
264                if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
265                    unset($GLOBALS[$key]);
266        }
267    }
268    /* }}} */
269    /* _handle_magic_quotes {{{ */
270    /**
271     * Detect wether magic_quotes_gpc is on.
272     * If so, it will disable it and get rid of all the automagically added slashes etc
273     */
274    protected function _handle_magic_quotes() {
275        /* check for magic_quotes_gpc. If on, remove the escape slashes */
276        set_magic_quotes_runtime(0);
277        if(get_magic_quotes_gpc() || ini_get("magic_quotes_sybase")) {
278            $_GET     = $this->_magic_quotes_strip($_GET);
279            $_POST    = $this->_magic_quotes_strip($_POST);
280            $_COOKIE  = $this->_magic_quotes_strip($_COOKIE);
281            $_REQUEST = array_merge($_GET, $_POST, $_COOKIE);
282            $_FILES   = $this->_magic_quotes_strip($_FILES);
283            $_ENV     = $this->_magic_quotes_strip($_ENV);
284            $_SERVER  = $this->_magic_quotes_strip($_SERVER);
285        }
286    }
287    /* }}} */
288    /* _magic_quotes_strip() {{{ */
289    /**
290     * strip escape \ signs when magic_quotes_gpc is on in php.ini
291     *
292     * @param mixed $mixed the input string or array
293     * @return mixed the string/array with the magic_quotes_gpc added slashes removed
294     */
295    protected function _magic_quotes_strip($mixed) {
296        if(is_array($mixed))
297            return array_map(array($this, "_magic_quotes_strip"), $mixed);
298        return stripslashes($mixed);
299    }
300    /* }}} */
301    /* _init_db {{{ */
302    /**
303     * Create database connection using the PEAR::MDB2 framework and puts this object in class variable db
304     */
305    protected function _init_db($settings = "") {
306        require_once("MDB2.php");
307        //sqlite has a different scheme, because it's filebased
308        if ($settings["type"] == "sqlite") {
309            if (is_dir("db"))
310                $dsn = sprintf("%s:///db/%s.db?mode=0666", $settings["type"], $settings["database"]);
311            elseif (is_dir("../db"))
312                $dsn = sprintf("%s:///../db/%s.db?mode=0666", $settings["type"], $settings["database"]);
313        } else {
314            $dsn = $settings["type"]."://".$settings["username"].":".$settings["password"]."@tcp(".$settings["hostname"].")/".$settings["database"];
315            $dsn = sprintf("%s://%s:%s@tcp(%s)/%s",
316                $settings["type"], $settings["username"], $settings["password"],
317                $settings["hostname"], $settings["database"]);
318        }
319
320        $options = array(
321            "debug"       => 2,
322            "portability" => MDB2_PORTABILITY_ALL,
323        );
324
325        $db =& MDB2::connect($dsn, $options);
326        if (PEAR::isError($db)) {
327            die($db->getMessage());
328        }
329        $this->db = $db;
330    }
331    /* }}} */
332    /* _sanitize {{{ */
333    /**
334     * Sanitize given data.
335     *
336     * This function will return an array if array was given, otherwise it will return the input.
337     * The array contents or the given string will be sanitized based on the optional options array.
338     * Key's with names that are outside a-zA-Z0-9_- will be removed.
339     *
340     * The optional options parameter is an array. The following keys will be handled:
341     * - bbcode: if true, besides the a-zA-Z0-9_- also @[]=/:+.? will be allowed
342     * - space: if true, also allow a whitespace character
343     * - url: if true, also allow :/+.?
344     * - email: if true, also allow @.
345     * if no options array is given, only a-zA-Z0-9_- will be allowed (that is without whitespace chars)
346     *
347     * @param mixed $data The data to sanitize
348     * @param array $options Options, see description for possible items
349     * @return mixed See description of the function for array structure
350     */
351    public function _sanitize($data, $options=array()) {
352        $allowed  = "a-zA-Z0-9_\-";
353        $allowed .= preg_quote("(){}");
354        /* create preg_replace string of characters we allow */
355        if (array_key_exists("bbcode", $options )) { $allowed .= preg_quote("![]&@=+:/.?;,'\\\"", "/"); }
356        if (array_key_exists("space", $options  )) { $allowed .= "\s"; }
357        if (array_key_exists("url", $options    )) { $allowed .= preg_quote("+:/.?", "/"); }
358        if (array_key_exists("email", $options  )) { $allowed .= preg_quote("@."); }
359
360        $allowed = "/[^$allowed]/s";
361        if (is_array($data)) {
362            /* lets handle the array */
363            foreach ($data as $k=>$v) {
364                /* if the data is an array too, recurse */
365                if (is_array($v))
366                    $v = $this->_sanitize($v, $options);
367
368                if (!preg_match("/[^a-zA-Z0-9_-]/s", $k)) {
369                    /* do the actual sanitizing */
370                    $_data[$k] = preg_replace($allowed, "", $v);
371                }
372            }
373        } else {
374            /* it's a string, lets process it */
375            $_data = preg_replace($allowed, "", $data);
376        }
377        return $_data;
378    }
379    /* }}} */
380    /* db_quote {{{ */
381    /**
382     * Quote a fieldname with the database specific quote style
383     *
384     * @param string fieldname to quote
385     * @return string the quoted version
386     */
387    public function db_quote($field) {
388        /* get the db type */
389        $dbtype = $this->db->dbsyntax;
390        switch ($dbtype) {
391            case "mysql" :
392                $return = "`".$field."`";
393                break;
394            case "pgsql" :
395                $return = "\"".$field."\"";
396                break;
397            default :
398                $return = $field;
399                break;
400        }
401        return $return;
402    }
403    /* }}} */
404    /* strip_invalid_xml() {{{ */
405    /**
406     * strip some stuff leftover from old editor
407     *
408     * @param string $data the text to process
409     * @return string the text with invalid xml stripped
410     */
411    public function strip_invalid_xml($data) {
412        $data = preg_replace("/ ref=\"[^\"].*\"/si", "", $data);
413        return $data;
414    }
415    /* }}} */
416    /* format parses */
417    /* parse_body {{{ */
418    /**
419     * Parse the body based on postformat.
420     *
421     * Supported formats with their processor:
422     * - BBC: bbcode - uses internal bbcode parser
423     * - HTML: html sourcecode - no processing is done
424     * - MW: MediaWiki sourcecode - uses PEAR::Text_Wiki_Mediawiki
425     *
426     * @param string $body The raw body data
427     * @param string $format The body format.
428     * @return string the formatted body.
429     */
430    public function parse_body($body, $format) {
431        switch ($format) {
432        case "BBC":
433            $body = $this->parse_bbcode($body);
434            break;
435        case "MW":
436            $body = $this->parse_mediawiki($body);
437            break;
438        }
439        //fix leftovers from old editor
440        $body = $this->strip_invalid_xml(stripslashes($body));
441        return $body;
442    }
443    /* }}} */
444    /* parse_bbcode($data) {{{ */
445    /**
446     * Parse a string and intepret bbcode
447     * @license LGPL
448     * @author Justin Palmer
449     * @url http://www.isolated-disigns.net/core
450     * @author Ferry Boender
451     *
452     * @param string $data The raw string to do bbcode substitution on
453     * @return string the string with bbcode tags substituded
454     */
455    public function parse_bbcode($data) {
456        #     AUTHOR:  JUSTIN PALMER
457        #     WEBSITE:  HTTP://WWW.ISOLATED-DESIGNS.NET/CORE
458        #     LICENSE: GNU LESSER GENERAL PUBLIC LICENSE http://www.gnu.org/copyleft/lesser.html
459        #     MODIFIED: Ferry Boender: optimized and function to strip bbcode
460        /* Strip useless newlines so [code] will appear correct */
461        $string = str_replace("\r", "", $data);
462        $string = str_replace("\n", "<br />", $string);
463
464        $patterns = array(
465            '`\[ul\](.+?)\[/ul\]`is',
466            '`\[li\](.+?)\[/li\]`is',
467            '`\[quote\](.+?)\[/quote\]`is',
468            '`\[indent](.+?)\[/indent\]`is',
469            '`\[code](.+?)\[/code\]`is',
470            '`\[b\](.+?)\[/b\]`is',
471            '`\[i\](.+?)\[/i\]`is',
472            '`\[u\](.+?)\[/u\]`is',
473            '`\[strike\](.+?)\[/strike\]`is',
474            '`\[color=#([0-9]{6})\](.+?)\[/color\]`is',
475            '`\[email\](.+?)\[/email\]`is',
476            '`\[img\](.+?)\[/img\]`is',
477            '`\[url=([a-z0-9]+://)([\w\-]+\.([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^ \"\n\r\t<]*?)?)\](.*?)\[/url\]`si',
478            '`\[url\]([a-z0-9]+?://){1}([\w\-]+\.([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^ \"\n\r\t<]*)?)\[/url\]`si',
479            '`\[url\]((www|ftp)\.([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^ \"\n\r\t<]*?)?)\[/url\]`si',
480            '`\[flash=([0-9]+),([0-9]+)\](.+?)\[/flash\]`is',
481            '`\[size=([1-6]+)\](.+?)\[/size\]`is'
482        );
483
484        $replaces =  array(
485            '<ul>\1</ul>',
486            '<li class="noblock">\1</li>',
487            '<span class="quote">\1</span>',
488            '<pre>\\1</pre>',
489            '<pre class="code">\\1</pre>',
490            '<strong>\\1</strong>',
491            '<em>\\1</em>',
492            '<span style="border-bottom: 1px dotted">\\1</span>',
493            '<strike>\\1</strike>',
494            '<span style="color:#\1;">\2</span>',
495            '<a href="mailto:\1">\1</a>',
496            '<img src="\1" alt="" style="border:0px;" />',
497            '<a href="\1\2">\6</a>',
498            '<a href="\1\2">\1\2</a>',
499            '<a href="http://\1">\1</a>',
500            '<object width="\1" height="\2"><param name="movie" value="\3" /><embed src="\3" width="\1" height="\2"></embed></object>',
501            '<h\1>\2</h\1>'
502        );
503
504        $prev_string = "";
505        while ($prev_string != $string) {
506            $prev_string = $string;
507            $string = preg_replace($patterns, $replaces , $string);
508        }
509        return(stripslashes($string));
510    }
511    /* }}} */
512    /* parse_mediawiki {{{ */
513    /**
514     * Parse Mediawiki formatted string into XHTML.
515     * This function uses PEAR::Text_Wiki and PEAR::Text_Wiki_Mediawiki.
516     * Because all plugins for Text_Wiki are alpha we include these 2 packages
517     * in the MvBlog sources.
518     *
519     * @param string $data The mediawiki source
520     * @return string XHTML representation of the wiki source
521     */
522    public function parse_mediawiki($data) {
523        //add our PEAR directory to the include path
524        set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__)."/../lib/");
525        //include the PEAR files
526        require_once("Text/Wiki.php");
527        //wiki object
528        $wiki =& Text_Wiki::singleton("Mediawiki");
529        $xhtml = $wiki->transform($data, "Xhtml");
530        unset($wiki);
531        return $xhtml;
532    }
533    /* }}} */
534    /* data getters */
535    /* _get_settings {{{ */
536    /**
537     * Get all the settings into an array
538     */
539    protected function _get_settings() {
540        $res =& $this->db->query("SELECT settingname, settingvalue FROM settings");
541        if (PEAR::isError($res)) {
542            die($res->getMessage());
543        }
544
545        // Add default settings as a fallback for when nothing's been set yet.
546        $settings = array();
547        $settings["blogtitle"]         = "";
548        $settings["blogdescription"]   = "";
549        $settings["blogkeywords"]      = "";
550        $settings["postsperpage"]      = 20;
551        $settings["cleanurl"]          = "";
552        $settings["allowanoncomments"] = 0;
553        $settings["dbversion"]         = 0;
554        $settings["show_cat_icons"]    = 0;
555        $settings["defaultpostformat"] = "HTML";
556        $settings["wysiwyg"]           = 0;
557
558        while ($row = $res->fetchRow(MDB2_FETCHMODE_ASSOC)) {
559            $settings[$row["settingname"]] = $row["settingvalue"];
560        }
561        if (array_key_exists("language", $settings))
562            $this->lang     = $settings["language"];
563        $this->settings = $settings;
564    }
565    /* }}} */
566    /* _get_webroot {{{ */
567    /**
568     * Get the webroot for the mvblog install
569     * Sets the value in class var webroot.
570     * This value will always end with a /
571     */
572    protected function _get_webroot() {
573        if (array_key_exists("bloglocation", $this->settings) && !empty($this->settings["bloglocation"])) {
574            $webroot = $this->settings["bloglocation"];
575        } else {
576            if (array_key_exists("HTTPS", $_SERVER) && strtolower($_SERVER["HTTPS"]) == "on") {
577                $webroot = "https://".strtolower($_SERVER["SERVER_NAME"]);
578                $httpsmode = 1;
579            } else {
580                $webroot = "http://".strtolower($_SERVER["SERVER_NAME"]);
581                $httpsmode = 0;
582            }
583            $webroot .= substr($_SERVER["SCRIPT_NAME"], 0, strpos($_SERVER["SCRIPT_NAME"], "index.php"));
584        }
585        /* if webroot does not end with a / append it */
586        if (substr($webroot, -1, 1) != "/")
587            $webroot .= "/";
588        $this->webroot = $webroot;
589    }
590    /* }}} */
591    /* _get_authors() {{{ */
592    /**
593     * Get all the authors into an array.
594     */
595    public function _get_authors() {
596        /* author with id 0 is for a new author. */
597        $authors = array(
598            0 => array(
599                "login"    => "login",
600                "password" => "",
601                "email"    => "",
602                "fullname" => "",
603                "active"   => 1,
604                "website"  => ""
605            )
606        );
607
608        $res =& $this->db->query("SELECT id,login