<?php /* * File: ratatoeskr/sys/models.php * Data models to make database accesses more comfortable. * * License: * This file is part of Ratatöskr. * Ratatöskr is licensed unter the MIT / X11 License. * See "ratatoeskr/licenses/ratatoeskr" for more information. */ require_once(dirname(__FILE__) . "/db.php"); require_once(dirname(__FILE__) . "/utils.php"); require_once(dirname(__FILE__) . "/../libs/kses.php"); require_once(dirname(__FILE__) . "/textprocessors.php"); require_once(dirname(__FILE__) . "/pluginpackage.php"); db_connect(); /* * Array: $imagetype_file_extensions * Array of default file extensions for most IMAGETYPE_* constants */ $imagetype_file_extensions = array( IMAGETYPE_GIF => "gif", IMAGETYPE_JPEG => "jpg", IMAGETYPE_PNG => "png", IMAGETYPE_BMP => "bmp", IMAGETYPE_TIFF_II => "tif", IMAGETYPE_TIFF_MM => "tif", ); /* * Variable: $ratatoeskr_settings * The global <Settings> object. Can be accessed like an array. * Has these fields: * * "default_language" - The Language code of the default language. * "comment_visible_default" - True, if comments should be visible by default. * "allow_comments_default" - True, if comments should be allowed by default. * "default_section" - The id of the default <Section>. * "comment_textprocessor" - The textprocessor to be used for comments. * "languages" - Array of activated languages. * "last_db_cleanup" - Timestamp of the last database cleanup. */ $ratatoeskr_settings = NULL; /* * Constants: ARTICLE_STATUS_ * Possible <Article>::$status values. * * ARTICLE_STATUS_HIDDEN - Article is hidden (Numeric: 0) * ARTICLE_STATUS_LIVE - Article is visible / live (Numeric: 1) * ARTICLE_STATUS_STICKY - Article is sticky (Numeric: 2) */ define("ARTICLE_STATUS_HIDDEN", 0); define("ARTICLE_STATUS_LIVE", 1); define("ARTICLE_STATUS_STICKY", 2); /* * Class: DoesNotExistError * This Exception is thrown by an ::by_*-constructor or any array-like object if the desired object is not present in the database. */ class DoesNotExistError extends Exception { } /* * Class: AlreadyExistsError * This Exception is thrown by an ::create-constructor or a save-method, if the creation/modification of the object would result in duplicates. */ class AlreadyExistsError extends Exception { } /* * Class: NotAllowedError */ class NotAllowedError extends Exception { } /* * Class: InvalidDataError * Exception that will be thrown, if a object with invalid data (e.g. urlname in this form not allowed) should have been saved / created. * Unless something else is said at a function, the exception message is a translation key. */ class InvalidDataError extends Exception { } abstract class BySQLRowEnabled { protected function __construct() { } abstract protected function populate_by_sqlrow($sqlrow); protected static function by_sqlrow($sqlrow) { $obj = new static(); $obj->populate_by_sqlrow($sqlrow); return $obj; } } /* * Class: KVStorage * An abstract class for a KVStorage. * * See also: * <PluginKVStorage>, <ArticleExtradata> */ abstract class KVStorage implements Countable, ArrayAccess, Iterator { private $keybuffer; private $counter; private $silent_mode; private $common_vals; private $stmt_get; private $stmt_unset; private $stmt_update; private $stmt_create; final protected function init($sqltable, $common) { $this->silent_mode = False; $this->keybuffer = array(); $selector = "WHERE "; $fields = ""; foreach($common as $field => $val) { $selector .= "`$field` = ? AND "; $fields .= ", `$field`"; $this->common_vals[] = $val; } $this->stmt_get = prep_stmt("SELECT `value` FROM `$sqltable` $selector `key` = ?"); $this->stmt_unset = prep_stmt("DELETE FROM `$sqltable` $selector `key` = ?"); $this->stmt_update = prep_stmt("UPDATE `$sqltable` SET `value` = ? $selector `key` = ?"); $this->stmt_create = prep_stmt("INSERT INTO `$sqltable` (`key`, `value` $fields) VALUES (?,?" . str_repeat(",?", count($common)) . ")"); $get_keys = prep_stmt("SELECT `key` FROM `$sqltable` $selector"); $get_keys->execute($this->common_vals); while($sqlrow = $get_keys->fetch()) $this->keybuffer[] = $sqlrow["key"]; $this->counter = 0; } /* * Functions: Silent mode * If the silent mode is enabled, the KVStorage behaves even more like a PHP array, i.e. it just returns NULL, * if a unknown key was requested and does not throw an DoesNotExistError Exception. * * enable_silent_mode - Enable the silent mode. * disable_silent_mode - Disable the silent mode (default). */ final public function enable_silent_mode() { $this->silent_mode = True; } final public function disable_silent_mode() { $this->silent_mode = False; } /* Countable interface implementation */ final public function count() { return count($this->keybuffer); } /* ArrayAccess interface implementation */ final public function offsetExists($offset) { return in_array($offset, $this->keybuffer); } final public function offsetGet($offset) { if($this->offsetExists($offset)) { $this->stmt_get->execute(array_merge($this->common_vals, array($offset))); $sqlrow = $this->stmt_get->fetch(); $this->stmt_get->closeCursor(); return unserialize(base64_decode($sqlrow["value"])); } elseif($this->silent_mode) return NULL; else throw new DoesNotExistError(); } final public function offsetUnset($offset) { if($this->offsetExists($offset)) { unset($this->keybuffer[array_search($offset, $this->keybuffer)]); $this->keybuffer = array_merge($this->keybuffer); $this->stmt_unset->execute(array_merge($this->common_vals, array($offset))); $this->stmt_unset->closeCursor(); } } final public function offsetSet($offset, $value) { if($this->offsetExists($offset)) { $this->stmt_update->execute(array_merge(array(base64_encode(serialize($value))), $this->common_vals, array($offset))); $this->stmt_update->closeCursor(); } else { $this->stmt_create->execute(array_merge(array($offset, base64_encode(serialize($value))), $this->common_vals)); $this->stmt_create->closeCursor(); $this->keybuffer[] = $offset; } } /* Iterator interface implementation */ final public function rewind() { return $this->counter = 0; } final public function current() { return $this->offsetGet($this->keybuffer[$this->counter]); } final public function key() { return $this->keybuffer[$this->counter]; } final public function next() { ++$this->counter; } final public function valid() { return isset($this->keybuffer[$this->counter]); } } /* * Class: User * Data model for Users */ class User extends BySQLRowEnabled { private $id; /* * Variables: Public class properties * * $username - The username. * $pwhash - <PasswordHash> of the password. * $mail - E-Mail-address. * $fullname - The full name of the user. * $language - Users language */ public $username; public $pwhash; public $mail; public $fullname; public $language; /* * Constructor: create * Creates a new user. * * Parameters: * $username - The username * $pwhash - <PasswordHash> of the password * * Returns: * An User object * * Throws: * <AlreadyExistsError> */ public static function create($username, $pwhash) { global $ratatoeskr_settings; global $db_con; try { $obj = self::by_name($name); } catch(DoesNotExistError $e) { global $ratatoeskr_settings; qdb("INSERT INTO `PREFIX_users` (`username`, `pwhash`, `mail`, `fullname`, `language`) VALUES (?, ?, '', '', ?)", $username, $pwhash, $ratatoeskr_settings["default_language"]); $obj = new self(); $obj->id = $db_con->lastInsertId(); $obj->username = $username; $obj->pwhash = $pwhash; $obj->mail = ""; $obj->fullname = ""; $obj->language = $ratatoeskr_settings["default_language"]; return $obj; } throw new AlreadyExistsError("\"$name\" is already in database."); } protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->username = $sqlrow["username"]; $this->pwhash = $sqlrow["pwhash"]; $this->mail = $sqlrow["mail"]; $this->fullname = $sqlrow["fullname"]; $this->language = $sqlrow["language"]; } /* * Constructor: by_id * Get a User object by ID * * Parameters: * $id - The ID. * * Returns: * An User object. * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `username`, `pwhash`, `mail`, `fullname`, `language` FROM `PREFIX_users` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if(!$sqlrow) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: by_name * Get a User object by username * * Parameters: * $username - The username. * * Returns: * An User object. * * Throws: * <DoesNotExistError> */ public static function by_name($username) { $stmt = qdb("SELECT `id`, `username`, `pwhash`, `mail`, `fullname`, `language` FROM `PREFIX_users` WHERE `username` = ?", $username); $sqlrow = $stmt->fetch(); if(!$sqlrow) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Function: all * Returns array of all available users. */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `username`, `pwhash`, `mail`, `fullname`, `language` FROM `PREFIX_users` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: get_id * Returns: * The user ID. */ public function get_id() { return $this->id; } /* * Function: save * Saves the object to database * * Throws: * AlreadyExistsError */ public function save() { transaction(function() { $stmt = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_users` WHERE `username` = ? AND `id` != ?", $this->username, $this->id); $sqlrow = $stmt->fetch(); if($sqlrow["n"] > 0) throw new AlreadyExistsError(); qdb("UPDATE `PREFIX_users` SET `username` = ?, `pwhash` = ?, `mail` = ?, `fullname` = ?, `language` = ? WHERE `id` = ?", $this->username, $this->pwhash, $this->mail, $this->fullname, $this->language, $this->id); }); } /* * Function: delete * Deletes the user from the database. * WARNING: Do NOT use this object any longer after you called this function! */ public function delete() { transaction(function() { qdb("DELETE FROM `PREFIX_group_members` WHERE `user` = ?", $this->id); qdb("DELETE FROM `PREFIX_users` WHERE `id` = ?", $this->id); }); } /* * Function: get_groups * Returns: * List of all groups where this user is a member (array of <Group> objects). */ public function get_groups() { $rv = array(); $stmt = qdb("SELECT `a`.`id` AS `id`, `a`.`name` AS `name` FROM `PREFIX_groups` `a` INNER JOIN `PREFIX_group_members` `b` ON `a`.`id` = `b`.`group` WHERE `b`.`user` = ?", $this->id); while($sqlrow = $stmt->fetch()) $rv[] = Group::by_sqlrow($sqlrow); return $rv; } /* * Function: member_of * Checks, if the user is a member of a group. * * Parameters: * $group - A Group object * * Returns: * True, if the user is a member of $group. False, if not. */ public function member_of($group) { $stmt = qdb("SELECT COUNT(*) AS `num` FROM `PREFIX_group_members` WHERE `user` = ? AND `group` = ?", $this->id, $group->get_id()); $sqlrow = $stmt->fetch(); return ($sqlrow["num"] > 0); } } /* * Class: Group * Data model for groups */ class Group extends BySQLRowEnabled { private $id; /* * Variables: Public class properties * * $name - Name of the group. */ public $name; /* * Constructor: create * Creates a new group. * * Parameters: * $name - The name of the group. * * Returns: * An Group object * * Throws: * <AlreadyExistsError> */ public static function create($name) { global $db_con; try { $obj = self::by_name($name); } catch(DoesNotExistError $e) { qdb("INSERT INTO `PREFIX_groups` (`name`) VALUES (?)", $name); $obj = new self(); $obj->id = $db_con->lastInsertId(); $obj->name = $name; return $obj; } throw new AlreadyExistsError("\"$name\" is already in database."); } protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->name = $sqlrow["name"]; } /* * Constructor: by_id * Get a Group object by ID * * Parameters: * $id - The ID. * * Returns: * A Group object. * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `name` FROM `PREFIX_groups` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if(!$sqlrow) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: by_name * Get a Group object by name * * Parameters: * $name - The group name. * * Returns: * A Group object. * * Throws: * <DoesNotExistError> */ public static function by_name($name) { $stmt = qdb("SELECT `id`, `name` FROM `PREFIX_groups` WHERE `name` = ?", $name); $sqlrow = $stmt->fetch(); if(!$sqlrow) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Function: all * Returns array of all groups */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `name` FROM `PREFIX_groups` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: get_id * Returns: * The group ID. */ public function get_id() { return $this->id; } /* * Function: delete * Deletes the group from the database. */ public function delete() { transaction(function() { qdb("DELETE FROM `PREFIX_group_members` WHERE `group` = ?", $this->id); qdb("DELETE FROM `PREFIX_groups` WHERE `id` = ?", $this->id); }); } /* * Function: get_members * Get all members of the group. * * Returns: * Array of <User> objects. */ public function get_members() { $rv = array(); $stmt = qdb("SELECT `a`.`id` AS `id`, `a`.`username` AS `username`, `a`.`pwhash` AS `pwhash`, `a`.`mail` AS `mail`, `a`.`fullname` AS `fullname`, `a`.`language` AS `language` FROM `PREFIX_users` `a` INNER JOIN `PREFIX_group_members` `b` ON `a`.`id` = `b`.`user` WHERE `b`.`group` = ?", $this->id); while($sqlrow = $stmt->fetch()) $rv[] = User::by_sqlrow($sqlrow); return $rv; } /* * Function: exclude_user * Excludes user from group. * * Parameters: * $user - <User> object. */ public function exclude_user($user) { qdb("DELETE FROM `PREFIX_group_members` WHERE `user` = ? AND `group` = ?", $user->get_id(), $this->id); } /* * Function: include_user * Includes user to group. * * Parameters: * $user - <User> object. */ public function include_user($user) { if(!$user->member_of($this)) qdb("INSERT INTO `PREFIX_group_members` (`user`, `group`) VALUES (?, ?)", $user->get_id(), $this->id); } } /* * Class: Translation * A translation. Can only be stored using an <Multilingual> object. */ class Translation { /* * Variables: Public class variables. * * $text - The translated text. * $texttype - The type of the text. Has only a meaning in a context. */ public $text; public $texttype; /* * Constructor: __construct * Creates a new Translation object. * IT WILL NOT BE STORED TO DATABASE! * * Parameters: * $text - The translated text. * $texttype - The type of the text. Has only a meaning in a context. * * See also: * <Multilingual> */ public function __construct($text, $texttype) { $this->text = $text; $this->texttype = $texttype; } } /* * Class: Multilingual * Container for <Translation> objects. * Translations can be accessed array-like. So, if you want the german translation: $translation = $my_multilingual["de"]; * * See also: * <languages.php> */ class Multilingual implements Countable, ArrayAccess, IteratorAggregate { private $translations; private $id; private $to_be_deleted; private $to_be_created; private function __construct() { $this->translations = array(); $this->to_be_deleted = array(); $this->to_be_created = array(); } /* * Function: get_id * Retuurns the ID of the object. */ public function get_id() { return $this->id; } /* * Constructor: create * Creates a new Multilingual object * * Returns: * An Multilingual object. */ public static function create() { global $db_con; $obj = new self(); qdb("INSERT INTO `PREFIX_multilingual` () VALUES ()"); $obj->id = $db_con->lastInsertId(); return $obj; } /* * Constructor: by_id * Gets an Multilingual object by ID. * * Parameters: * $id - The ID. * * Returns: * An Multilingual object. * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $obj = new self(); $stmt = qdb("SELECT `id` FROM `PREFIX_multilingual` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if($sqlrow == False) throw new DoesNotExistError(); $obj->id = $id; $stmt = qdb("SELECT `language`, `text`, `texttype` FROM `PREFIX_translations` WHERE `multilingual` = ?", $id); while($sqlrow = $stmt->fetch()) $obj->translations[$sqlrow["language"]] = new Translation($sqlrow["text"], $sqlrow["texttype"]); return $obj; } /* * Function: save * Saves the translations to database. */ public function save() { transaction(function() { foreach($this->to_be_deleted as $deletelang) qdb("DELETE FROM `PREFIX_translations` WHERE `multilingual` = ? AND `language` = ?", $this->id, $deletelang); foreach($this->to_be_created as $lang) qdb("INSERT INTO `PREFIX_translations` (`multilingual`, `language`, `text`, `texttype`) VALUES (?, ?, ?, ?)", $this->id, $lang, $this->translations[$lang]->text, $this->translations[$lang]->texttype); foreach($this->translations as $lang => $translation) { if(!in_array($lang, $this->to_be_created)) qdb("UPDATE `PREFIX_translations` SET `text` = ?, `texttype` = ? WHERE `multilingual` = ? AND `language` = ?", $translation->text, $translation->texttype, $this->id, $lang); } $this->to_be_deleted = array(); $this->to_be_created = array(); }); } /* * Function: delete * Deletes the data from database. */ public function delete() { transaction(function() { qdb("DELETE FROM `PREFIX_translations` WHERE `multilingual` = ?", $this->id); qdb("DELETE FROM `PREFIX_multilingual` WHERE `id` = ?", $this->id); }); } /* Countable interface implementation */ public function count() { return count($this->languages); } /* ArrayAccess interface implementation */ public function offsetExists($offset) { return isset($this->translations[$offset]); } public function offsetGet($offset) { if(isset($this->translations[$offset])) return $this->translations[$offset]; else throw new DoesNotExistError(); } public function offsetUnset($offset) { unset($this->translations[$offset]); if(in_array($offset, $this->to_be_created)) unset($this->to_be_created[array_search($offset, $this->to_be_created)]); else $this->to_be_deleted[] = $offset; } public function offsetSet($offset, $value) { if(!isset($this->translations[$offset])) { if(in_array($offset, $this->to_be_deleted)) unset($this->to_be_deleted[array_search($offset, $this->to_be_deleted)]); else $this->to_be_created[] = $offset; } $this->translations[$offset] = $value; } /* IteratorAggregate interface implementation */ public function getIterator() { return new ArrayIterator($this->translations); } } class SettingsIterator implements Iterator { private $index; private $keys; private $settings_obj; public function __construct($settings_obj, $keys) { $this->index = 0; $this->settings_obj = $settings_obj; $this->keys = $keys; } /* Iterator implementation */ public function current() { return $this->settings_obj[$this->keys[$this->index]]; } public function key() { return $this->keys[$this->index]; } public function next() { ++$this->index; } public function rewind() { $this->index = 0; } public function valid() { return $this->index < count($this->keys); } } /* * Class: Settings * A class that holds the Settings of Ratatöskr. * You can access settings like an array. */ class Settings implements ArrayAccess, IteratorAggregate, Countable { /* Singleton implementation */ private function __copy() {} private static $instance = NULL; /* * Constructor: get_instance * Get an instance of this class. * All instances are equal (ie. this is a singleton), so you can also use * the global <$ratatoeskr_settings> instance. */ public static function get_instance() { if(self::$instance === NULL) self::$instance = new self; return self::$instance; } private $buffer; private $to_be_deleted; private $to_be_created; private $to_be_updated; private function __construct() { $this->buffer = array(); $stmt = qdb("SELECT `key`, `value` FROM `PREFIX_settings_kvstorage` WHERE 1"); while($sqlrow = $stmt->fetch()) $this->buffer[$sqlrow["key"]] = unserialize(base64_decode($sqlrow["value"])); $this->to_be_created = array(); $this->to_be_deleted = array(); $this->to_be_updated = array(); } public function save() { transaction(function(){ foreach($this->to_be_deleted as $k) qdb("DELETE FROM `PREFIX_settings_kvstorage` WHERE `key` = ?", $k); foreach($this->to_be_updated as $k) qdb("UPDATE `PREFIX_settings_kvstorage` SET `value` = ? WHERE `key` = ?", base64_encode(serialize($this->buffer[$k])), $k); foreach($this->to_be_created as $k) qdb("INSERT INTO `PREFIX_settings_kvstorage` (`key`, `value`) VALUES (?, ?)", $k, base64_encode(serialize($this->buffer[$k]))); $this->to_be_created = array(); $this->to_be_deleted = array(); $this->to_be_updated = array(); }); } /* ArrayAccess implementation */ public function offsetExists($offset) { return isset($this->buffer[$offset]); } public function offsetGet($offset) { return $this->buffer[$offset]; } public function offsetSet ($offset, $value) { if(!$this->offsetExists($offset)) { if(in_array($offset, $this->to_be_deleted)) { $this->to_be_updated[] = $offset; unset($this->to_be_deleted[array_search($offset, $this->to_be_deleted)]); } else $this->to_be_created[] = $offset; } elseif((!in_array($offset, $this->to_be_created)) and (!in_array($offset, $this->to_be_updated))) $this->to_be_updated[] = $offset; $this->buffer[$offset] = $value; } public function offsetUnset($offset) { if(in_array($offset, $this->to_be_created)) unset($this->to_be_created[array_search($offset, $this->to_be_created)]); else $this->to_be_deleted[] = $offset; unset($this->buffer[$offset]); } /* IteratorAggregate implementation */ public function getIterator() { return new SettingsIterator($this, array_keys($this->buffer)); } /* Countable implementation */ public function count() { return count($this->buffer); } } $ratatoeskr_settings = Settings::get_instance(); /* * Class: PluginKVStorage * A Key-Value-Storage for Plugins * Can be accessed like an array. * Keys are strings and Values can be everything serialize() can process. * * Extends the abstract <KVStorage> class. */ class PluginKVStorage extends KVStorage { /* * Constructor: __construct * * Parameters: * $plugin_id - The ID of the Plugin. */ public function __construct($plugin_id) { $this->init("PREFIX_plugin_kvstorage", array("plugin" => $plugin_id)); } } /* * Class: Comment * Representing a user comment */ class Comment extends BySQLRowEnabled { private $id; private $article_id; private $language; private $timestamp; /* * Variables: Public class variables. * * $author_name - Name of comment author. * $author_mail - E-Mail of comment author. * $text - Comment text. * $visible - Should the comment be visible? * $read_by_admin - Was the comment read by an admin. */ public $author_name; public $author_mail; public $text; public $visible; public $read_by_admin; /* * Functions: Getters * * get_id - Gets the comment ID. * get_article - Gets the article. * get_language - Gets the language. * get_timestamp - Gets the timestamp. */ public function get_id() { return $this->id; } public function get_article() { return Article::by_id($this->article_id); } public function get_language() { return $this->language; } public function get_timestamp() { return $this->timestamp; } /* * Constructor: create * Creates a new comment. * Automatically sets the $timestamp and $visible (default from setting "comment_visible_default"). * * Parameters: * $article - An <Article> Object. * $language - Which language? (see <languages.php>) */ public static function create($article, $language) { global $ratatoeskr_settings; global $db_con; $obj = new self(); $obj->timestamp = time(); qdb("INSERT INTO `PREFIX_comments` (`article`, `language`, `author_name`, `author_mail`, `text`, `timestamp`, `visible`, `read_by_admin`) VALUES (?, ?, '', '', '', ?, ?, 0)", $article->get_id(), $language, $obj->timestamp, $ratatoeskr_settings["comment_visible_default"] ? 1 : 0); $obj->id = $db_con->lastInsertId(); $obj->article_id = $article->get_id(); $obj->language = $language; $obj->author_name = ""; $obj->author_mail = ""; $obj->text = ""; $obj->visible = $ratatoeskr_settings["comment_visible_default"]; $obj->read_by_admin = False; return $obj; } protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->article_id = $sqlrow["article"]; $this->language = $sqlrow["language"]; $this->author_name = $sqlrow["author_name"]; $this->author_mail = $sqlrow["author_mail"]; $this->text = $sqlrow["text"]; $this->timestamp = $sqlrow["timestamp"]; $this->visible = $sqlrow["visible"] == 1; $this->read_by_admin = $sqlrow["read_by_admin"] == 1; } /* * Constructor: by_id * Gets a Comment by ID. * * Parameters: * $id - The comments ID. * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `article`, `language`, `author_name`, `author_mail`, `text`, `timestamp`, `visible`, `read_by_admin` FROM `PREFIX_comments` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: all * Get all comments * * Returns: * Array of Comment objects */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `article`, `language`, `author_name`, `author_mail`, `text`, `timestamp`, `visible`, `read_by_admin` FROM `PREFIX_comments` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: htmlize_comment_text * Creates the HTML representation of a comment text. It applys the page's comment textprocessor on it * and filters some potentially harmful tags using kses. * * Parameters: * $text - Text to HTMLize. * * Returns: * HTML code. */ public static function htmlize_comment_text($text) { global $ratatoeskr_settings; return kses(textprocessor_apply($text, $ratatoeskr_settings["comment_textprocessor"]), array( "a" => array("href" => 1, "hreflang" => 1, "title" => 1, "rel" => 1, "rev" => 1), "b" => array(), "i" => array(), "u" => array(), "strong" => array(), "em" => array(), "p" => array("align" => 1), "br" => array(), "abbr" => array(), "acronym" => array(), "code" => array(), "pre" => array(), "blockquote" => array("cite" => 1), "h1" => array(), "h2" => array(), "h3" => array(), "h4" => array(), "h5" => array(), "h6" => array(), "img" => array("src" => 1, "alt" => 1, "width" => 1, "height" => 1), "s" => array(), "q" => array("cite" => 1), "samp" => array(), "ul" => array(), "ol" => array(), "li" => array(), "del" => array(), "ins" => array(), "dl" => array(), "dd" => array(), "dt" => array(), "dfn" => array(), "div" => array(), "dir" => array(), "kbd" => array("prompt" => 1), "strike" => array(), "sub" => array(), "sup" => array(), "table" => array("style" => 1), "tbody" => array(), "thead" => array(), "tfoot" => array(), "tr" => array(), "td" => array("colspan" => 1, "rowspan" => 1), "th" => array("colspan" => 1, "rowspan" => 1), "tt" => array(), "var" => array() )); } /* * Function: create_html * Applys <htmlize_comment_text> onto this comment's text. * * Returns: * The HTML representation. */ public function create_html() { return self::htmlize_comment_text($this->text); } /* * Function: save * Save changes to database. */ public function save() { qdb("UPDATE `PREFIX_comments` SET `author_name` = ?, `author_mail` = ?, `text` = ?, `visible` = ?, `read_by_admin` = ? WHERE `id` = ?", $this->author_name, $this->author_mail, $this->text, ($this->visible ? 1 : 0), ($this->read_by_admin ? 1 : 0), $this->id); } /* * Function: delete */ public function delete() { qdb("DELETE FROM `PREFIX_comments` WHERE `id` = ?", $this->id); } } /* * Class: Style * Represents a Style */ class Style extends BySQLRowEnabled { private $id; /* * Variables: Public class variables. * * $name - The name of the style. * $code - The CSS code. */ public $name; public $code; protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->name = $sqlrow["name"]; $this->code = $sqlrow["code"]; } /* * Function: test_name * Test, if a name is a valid Style name. * * Parameters: * $name - The name to test * * Returns: * True, if the name is a valid style name, False if not. */ public static function test_name($name) { return preg_match("/^[a-zA-Z0-9\\-_\\.]+$/", $name) == 1; } /* * Function: get_id */ public function get_id() { return $this->id; } /* * Constructor: create * Create a new style. * * Parameters: * $name - A name for the new style. * * Throws: * <AlreadyExistsError> */ public static function create($name) { global $db_con; if(!self::test_name($name)) throw new InvalidDataError("invalid_style_name"); try { self::by_name($name); } catch(DoesNotExistError $e) { $obj = new self(); $obj->name = $name; $obj->code = ""; qdb("INSERT INTO `PREFIX_styles` (`name`, `code`) VALUES (?, '')", $name); $obj->id = $db_con->lastInsertId(); return $obj; } throw new AlreadyExistsError(); } /* * Constructor: by_id * Gets a Style object by ID. * * Parameters: * $id - The ID * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `name`, `code` FROM `PREFIX_styles` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if(!$sqlrow) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: by_name * Gets a Style object by name. * * Parameters: * $name - The name. * * Throws: * <DoesNotExistError> */ public static function by_name($name) { $stmt = qdb("SELECT `id`, `name`, `code` FROM `PREFIX_styles` WHERE `name` = ?", $name); $sqlrow = $stmt->fetch(); if(!$sqlrow) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: all * Get all styles * * Returns: * Array of Style objects */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `name`, `code` FROM `PREFIX_styles` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: save * Save changes to database. * * Throws: * <AlreadyExistsError> */ public function save() { if(!self::test_name($name)) throw new InvalidDataError("invalid_style_name"); transaction(function() { $stmt = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_styles` WHERE `name` = ? AND `id` != ?", $this->name, $this->id); $sqlrow = $stmt->fetch(); if($sqlrow["n"] > 0) throw new AlreadyExistsError(); qdb("UPDATE `PREFIX_styles` SET `name` = ?, `code` = ? WHERE `id` = ?", $this->name, $this->code, $this->id); }); } /* * Function: delete */ public function delete() { transaction(function(){ qdb("DELETE FROM `PREFIX_styles` WHERE `id` = ?", $this->id); qdb("DELETE FROM `PREFIX_section_style_relations` WHERE `style` = ?", $this->id); }); } } /* * Class: Plugin * The representation of a plugin in the database. */ class Plugin extends BySQLRowEnabled { private $id; /* * Variables: Public class variables. * * $name - Plugin name. * $code - Plugin code. * $classname - Main class of the plugin. * $active - Is the plugin activated? * $author - Author of the plugin. * $versiontext - Version (text) * $versioncount - Version (counter) * $short_description - A short description. * $updatepath - URL for updates. * $web - Webpage of the plugin. * $help - Help page. * $license - License text. * $installed - Is this plugin installed? Used during the installation process. * $update - Should the plugin be updated at next start? * $api - The API version this Plugin needs. */ public $name; public $code; public $classname; public $active; public $author; public $versiontext; public $versioncount; public $short_description; public $updatepath; public $web; public $help; public $license; public $installed; public $update; public $api; /* * Function: clean_db * Performs some datadase cleanup jobs on the plugin table. */ public static function clean_db() { qdb("DELETE FROM `PREFIX_plugins` WHERE `installed` = 0 AND `added` < ?", (time() - (60*5))); } /* * Function: get_id */ public function get_id() { return $this->id; } /* * Constructor: create * Creates a new, empty plugin database entry */ public static function create() { global $db_con; $obj = new self(); qdb("INSERT INTO `PREFIX_plugins` (`added`) VALUES (?)", time()); $obj->id = $db_con->lastInsertId(); return $obj; } /* * Function: fill_from_pluginpackage * Fills plugin data from an <PluginPackage> object. * * Parameters: * $pkg - The <PluginPackage> object. */ public function fill_from_pluginpackage($pkg) { $this->name = $pkg->name; $this->code = $pkg->code; $this->classname = $pkg->classname; $this->author = $pkg->author; $this->versiontext = $pkg->versiontext; $this->versioncount = $pkg->versioncount; $this->short_description = $pkg->short_description; $this->updatepath = $pkg->updatepath; $this->web = $pkg->web; $this->license = $pkg->license; $this->help = $pkg->help; $this->api = $pkg->api; if(!empty($pkg->custompub)) array2dir($pkg->custompub, dirname(__FILE__) . "/../plugin_extradata/public/" . $this->get_id()); if(!empty($pkg->custompriv)) array2dir($pkg->custompriv, dirname(__FILE__) . "/../plugin_extradata/private/" . $this->get_id()); if(!empty($pkg->tpls)) array2dir($pkg->tpls, dirname(__FILE__) . "/../templates/src/plugintemplates/" . $this->get_id()); } protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->name = $sqlrow["name"]; $this->code = $sqlrow["code"]; $this->classname = $sqlrow["classname"]; $this->active = ($sqlrow["active"] == 1); $this->author = $sqlrow["author"]; $this->versiontext = $sqlrow["versiontext"]; $this->versioncount = $sqlrow["versioncount"]; $this->short_description = $sqlrow["short_description"]; $this->updatepath = $sqlrow["updatepath"]; $this->web = $sqlrow["web"]; $this->help = $sqlrow["help"]; $this->license = $sqlrow["license"]; $this->installed = ($sqlrow["installed"] == 1); $this->update = ($sqlrow["update"] == 1); $this->api = $sqlrow["api"]; } /* * Constructor: by_id * Gets plugin by ID. * * Parameters: * $id - The ID * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `name`, `author`, `versiontext`, `versioncount`, `short_description`, `updatepath`, `web`, `help`, `code`, `classname`, `active`, `license`, `installed`, `update`, `api` FROM `PREFIX_plugins` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: all * Gets all Plugins * * Returns: * List of <Plugin> objects. */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `name`, `author`, `versiontext`, `versioncount`, `short_description`, `updatepath`, `web`, `help`, `code`, `classname`, `active`, `license`, `installed`, `update`, `api` FROM `PREFIX_plugins` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: save */ public function save() { qdb("UPDATE `PREFIX_plugins` SET `name` = ?, `author` = ?, `code` = ?, `classname` = ?, `active` = ?, `versiontext` = ?, `versioncount` = ?, `short_description` = ?, `updatepath` = ?, `web` = ?, `help` = ?, `installed` = ?, `update` = ?, `license` = ?, `api` = ? WHERE `id` = ?", $this->name, $this->author, $this->code, $this->classname, ($this->active ? 1 : 0), $this->versiontext, $this->versioncount, $this->short_description, $this->updatepath, $this->web, $this->help, ($this->installed ? 1 : 0), ($this->update ? 1 : 0), $this->license, $this->api, $this->id); } /* * Function: delete */ public function delete() { transaction(function() { qdb("DELETE FROM `PREFIX_plugins` WHERE `id` = ?", $this->id); qdb("DELETE FROM `PREFIX_plugin_kvstorage` WHERE `plugin` = ?", $this->id); qdb("DELETE FROM `PREFIX_article_extradata` WHERE `plugin` = ?", $this->id); }); if(is_dir(SITE_BASE_PATH . "/ratatoeskr/plugin_extradata/private/" . $this->id)) delete_directory(SITE_BASE_PATH . "/ratatoeskr/plugin_extradata/private/" . $this->id); if(is_dir(SITE_BASE_PATH . "/ratatoeskr/plugin_extradata/public/" . $this->id)) delete_directory(SITE_BASE_PATH . "/ratatoeskr/plugin_extradata/public/" . $this->id); if(is_dir(SITE_BASE_PATH . "/ratatoeskr/templates/src/plugintemplates/" . $this->id)) delete_directory(SITE_BASE_PATH . "/ratatoeskr/templates/src/plugintemplates/" . $this->id); } /* * Function get_kvstorage * Get the KeyValue Storage for the plugin. * * Returns: * An <PluginKVStorage> object. */ public function get_kvstorage() { return new PluginKVStorage($this->id); } } /* * Class: Section * Representing a section */ class Section extends BySQLRowEnabled { private $id; /* * Variables: Public class variables * * $name - The name of the section. * $title - The title of the section (a <Multilingual> object). * $template - Name of the template. */ public $name; public $title; public $template; protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->name = $sqlrow["name"]; $this->title = Multilingual::by_id($sqlrow["title"]); $this->template = $sqlrow["template"]; } /* * Function: test_name * Tests, if a name is a valid section name. * * Parameters: * $name - The name to test. * * Returns: * True, if the name is a valid section name, False otherwise. */ public static function test_name($name) { return preg_match("/^[a-zA-Z0-9\\-_]+$/", $name) != 0; } /* * Function: get_id */ public function get_id() { return $this->id; } /* * Constructor: create * Creates a new section. * * Parameters: * $name - The name of the new section. * * Throws: * <AlreadyExistsError>, <InvalidDataError> */ public static function create($name) { global $db_con; if(!self::test_name($name)) throw new InvalidDataError("invalid_section_name"); try { $obj = self::by_name($name); } catch(DoesNotExistError $e) { $obj = new self(); $obj->name = $name; $obj->title = Multilingual::create(); $obj->template = ""; qdb("INSERT INTO `PREFIX_sections` (`name`, `title`, `template`) VALUES (?, ?, '')", $name, $obj->title->get_id()); $obj->id = $db_con->lastInsertId(); return $obj; } throw new AlreadyExistsError(); } /* * Constructor: by_id * Gets section by ID. * * Parameters: * $id - The ID. * * Returns: * A <Section> object. * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `name`, `title`, `template` FROM `PREFIX_sections` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: by_name * Gets section by name. * * Parameters: * $name - The name. * * Returns: * A <Section> object. * * Throws: * <DoesNotExistError> */ public static function by_name($name) { $stmt = qdb("SELECT `id`, `name`, `title`, `template` FROM `PREFIX_sections` WHERE `name` = ?", $name); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: all * Gets all sections. * * Returns: * Array of Section objects. */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `name`, `title`, `template` FROM `PREFIX_sections` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: get_styles * Get all styles associated with this section. * * Returns: * List of <Style> objects. */ public function get_styles() { $rv = array(); $stmt = qdb("SELECT `a`.`id` AS `id`, `a`.`name` AS `name`, `a`.`code` AS `code` FROM `PREFIX_styles` `a` INNER JOIN `PREFIX_section_style_relations` `b` ON `a`.`id` = `b`.`style` WHERE `b`.`section` = ?", $this->id); while($sqlrow = $stmt->fetch()) $rv[] = Style::by_sqlrow($sqlrow); return $rv; } /* * Function: add_style * Add a style to this section. * * Parameters: * $style - A <Style> object. */ public function add_style($style) { transaction(function() use ($style) { $stmt = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_section_style_relations` WHERE `style` = ? AND `section` = ?", $style->get_id(), $this->id); $sqlrow = $stmt->fetch(); if($sqlrow["n"] == 0) qdb("INSERT INTO `PREFIX_section_style_relations` (`section`, `style`) VALUES (?, ?)", $this->id, $style->get_id()); }); } /* * Function: remove_style * Remove a style from this section. * * Parameters: * $style - A <Style> object. */ public function remove_style($style) { qdb("DELETE FROM `PREFIX_section_style_relations` WHERE `section` = ? AND `style` = ?", $this->id, $style->get_id()); } /* * Function: save * * Throws: * <AlreadyExistsError>, <InvalidDataError> */ public function save() { if(!self::test_name($name)) throw new InvalidDataError("invalid_section_name"); transaction(function() { $stmt = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_sections` WHERE `name` = ? AND `id` != ?", $this->name, $this->id); $sqlrow = $stmt->fetch(); if($sqlrow["n"] > 0) throw new AlreadyExistsError(); $this->title->save(); qdb("UPDATE `PREFIX_sections` SET `name` = ?, `title` = ?, `template` = ? WHERE `id` = ?", $this->name, $this->title->get_id(), $this->template, $this->id); }); } /* * Function: delete */ public function delete() { transaction(function() { $this->title->delete(); qdb("DELETE FROM `PREFIX_sections` WHERE `id` = ?", $this->id); qdb("DELETE FROM `PREFIX_section_style_relations` WHERE `section` = ?", $this->id); }); } /* * Function: get_articles * Get all articles in this section. * * Returns: * Array of <Article> objects */ public function get_articles() { $rv = array(); $stmt = qdb("SELECT `id`, `urlname`, `title`, `text`, `excerpt`, `meta`, `custom`, `article_image`, `status`, `section`, `timestamp`, `allow_comments` FROM `PREFIX_articles` WHERE `section` = ?", $this->id); while($sqlrow = $stmt->fetch()) $rv[] = Article::by_sqlrow($sqlrow); return $rv; } } /* * Class: Tag * Representation of a tag */ class Tag extends BySQLRowEnabled { private $id; /* * Variables: Public class variables * * $name - The name of the tag * $title - The title (an <Multilingual> object) */ public $name; public $title; /* * Function: test_name * Test, if a name is a valid tag name. * * Parameters: * $name - Name to test. * * Returns: * True, if the name is valid, False otherwise. */ public static function test_name($name) { return (strpos($name, ",") === False) and (strpos($name, " ") === False); } /* * Function: get_id */ public function get_id() { return $this->id; } protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->name = $sqlrow["name"]; $this->title = Multilingual::by_id($sqlrow["title"]); } /* * Constructor: create * Create a new tag. * * Parameters: * $name - The name * * Throws: * <AlreadyExistsError>, <InvalidDataError> */ public static function create($name) { global $db_con; if(!self::test_name($name)) throw new InvalidDataError("invalid_tag_name"); try { $obj = self::by_name($name); } catch(DoesNotExistError $e) { $obj = new self(); $obj->name = $name; $obj->title = Multilingual::create(); qdb("INSERT INTO `PREFIX_tags` (`name`, `title`) VALUES (?, ?)", $name, $obj->title->get_id()); $obj->id = $db_con->lastInsertId(); return $obj; } throw new AlreadyExistsError(); } /* * Constructor: by_id * Get tag by ID * * Parameters: * $id - The ID * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `name`, `title` FROM `PREFIX_tags` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: by_name * Get tag by name * * Parameters: * $name - The name * * Throws: * <DoesNotExistError> */ public static function by_name($name) { $stmt = qdb("SELECT `id`, `name`, `title` FROM `PREFIX_tags` WHERE `name` = ?", $name); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: all * Get all tags * * Returns: * Array of Tag objects. */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `name`, `title` FROM `PREFIX_tags` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: get_articles * Get all articles that are tagged with this tag * * Returns: * Array of <Article> objects */ public function get_articles() { $rv = array(); $stmt = qdb( "SELECT `a`.`id` AS `id`, `a`.`urlname` AS `urlname`, `a`.`title` AS `title`, `a`.`text` AS `text`, `a`.`excerpt` AS `excerpt`, `a`.`meta` AS `meta`, `a`.`custom` AS `custom`, `a`.`article_image` AS `article_image`, `a`.`status` AS `status`, `a`.`section` AS `section`, `a`.`timestamp` AS `timestamp`, `a`.`allow_comments` AS `allow_comments` FROM `PREFIX_articles` `a` INNER JOIN `PREFIX_article_tag_relations` `b` ON `a`.`id` = `b`.`article` WHERE `b`.`tag` = ?" , $this->id); while($sqlrow = $stmt->fetch()) $rv[] = Article::by_sqlrow($sqlrow); return $rv; } /* * Function: count_articles * * Returns: * The number of articles that are tagged with this tag. */ public function count_articles() { $stmt = qdb("SELECT COUNT(*) AS `num` FROM `PREFIX_article_tag_relations` WHERE `tag` = ?", $this->id); $sqlrow = $stmt->fetch(); return $sqlrow["num"]; } /* * Function: save * * Throws: * <AlreadyExistsError>, <InvalidDataError> */ public function save() { if(!self::test_name($name)) throw new InvalidDataError("invalid_tag_name"); transaction(function() { $stmt = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_tags` WHERE `name` = ? AND `id` != ?", $this->name, $this->id); $sqlrow = $stmt->fetch(); if($sqlrow["n"] > 0) throw new AlreadyExistsError(); $this->title->save(); qdb("UPDATE `PREFIX_tags` SET `name` = ?, `title` = ? WHERE `id` = ?", $this->name, $this->title->get_id(), $this->id); }); } /* * Function: delete */ public function delete() { transaction(function() { $this->title->delete(); qdb("DELETE FROM `PREFIX_article_tag_relations` WHERE `tag` = ?", $this->id); qdb("DELETE FROM `PREFIX_tags` WHERE `id` = ?", $this->id); }); } } /* * Class: UnknownFileFormat * Exception that will be thrown, if a input file has an unsupported file format. */ class UnknownFileFormat extends Exception { } /* * Class: IOError * This Exception is thrown, if a IO-Error occurs (file not available, no read/write acccess...). */ class IOError extends Exception { } /* * Class: Image * Representation of an image entry. */ class Image extends BySQLRowEnabled { private $id; private $filename; private static $pre_maxw = 150; private static $pre_maxh = 100; /* * Variables: Public class variables * * $name - The image name */ public $name; protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->name = $sqlrow["name"]; $this->file = $sqlrow["file"]; } /* * Functions: Getters * * get_id - Get the ID * get_filename - Get the filename */ public function get_id() { return $this->id; } public function get_filename() { return $this->file; } /* * Constructor: create * Create a new image * * Parameters: * $name - The name for the image * $file - An uploaded image file (move_uploaded_file must be able to move the file!). * * Throws: * <IOError>, <UnknownFileFormat> */ public static function create($name, $file) { $obj = new self(); $obj->name = $name; $obj->file = "0"; transaction(function() use (&$obj, $name, $file) { global $db_con; qdb("INSERT INTO `PREFIX_images` (`name`, `file`) VALUES (?, '0')", $name); $obj->id = $db_con->lastInsertId(); $obj->exchange_image($file); }); return $obj; } /* * Constructor: by_id * Get image by ID. * * Parameters: * $id - The ID * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `name`, `file` FROM `PREFIX_images` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: all * Gets all images. * * Returns: * Array of <Image> objects. */ public function all() { $rv = array(); $stmt = qdb("SELECT `id`, `name`, `file` FROM `PREFIX_images` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: exchange_image * Exchanges image file. Also saves object to database. * * Parameters: * $file - Location of new image.(move_uploaded_file must be able to move the file!) * * Throws: * <IOError>, <UnknownFileFormat> */ public function exchange_image($file) { global $imagetype_file_extensions; if(!is_file($file)) throw new IOError("\"$file\" is not available"); $imageinfo = getimagesize($file); if($imageinfo === False) throw new UnknownFileFormat(); if(!isset($imagetype_file_extensions[$imageinfo[2]])) throw new UnknownFileFormat(); if(is_file(SITE_BASE_PATH . "/images/" . $this->file)) unlink(SITE_BASE_PATH . "/images/" . $this->file); $new_fn = $this->id . "." . $imagetype_file_extensions[$imageinfo[2]]; if(!move_uploaded_file($file, SITE_BASE_PATH . "/images/" . $new_fn)) throw new IOError("Can not move file."); $this->file = $new_fn; $this->save(); /* make preview image */ switch($imageinfo[2]) { case IMAGETYPE_GIF: $img = imagecreatefromgif (SITE_BASE_PATH . "/images/" . $new_fn); break; case IMAGETYPE_JPEG: $img = imagecreatefromjpeg(SITE_BASE_PATH . "/images/" . $new_fn); break; case IMAGETYPE_PNG: $img = imagecreatefrompng (SITE_BASE_PATH . "/images/" . $new_fn); break; default: $img = imagecreatetruecolor(40, 40); imagefill($img, 1, 1, imagecolorallocate($img, 127, 127, 127)); break; } $w_orig = imagesx($img); $h_orig = imagesy($img); if(($w_orig > self::$pre_maxw) or ($h_orig > self::$pre_maxh)) { $ratio = $w_orig / $h_orig; if($ratio > 1) { $w_new = round(self::$pre_maxw); $h_new = round(self::$pre_maxw / $ratio); } else { $h_new = round(self::$pre_maxh); $w_new = round(self::$pre_maxh * $ratio); } $preview = imagecreatetruecolor($w_new, $h_new); imagecopyresized($preview, $img, 0, 0, 0, 0, $w_new, $h_new, $w_orig, $h_orig); imagepng($preview, SITE_BASE_PATH . "/images/previews/{$this->id}.png"); } else imagepng($img, SITE_BASE_PATH . "/images/previews/{$this->id}.png"); } /* * Function: save */ public function save() { qdb("UPDATE `PREFIX_images` SET `name` = ?, `file` = ? WHERE `id` = ?", $this->name, $this->file, $this->id); } /* * Function: delete */ public function delete() { qdb("DELETE FROM `PREFIX_images` WHERE `id` = ?", $this->id); if(is_file(SITE_BASE_PATH . "/images/" . $this->file)) unlink(SITE_BASE_PATH . "/images/" . $this->file); if(is_file(SITE_BASE_PATH . "/images/previews/{$this->id}.png")) unlink(SITE_BASE_PATH . "/images/previews/{$this->id}.png"); } } /* * Class: RepositoryUnreachableOrInvalid * A Exception that will be thrown, if the repository is unreachable or seems to be an invalid repository. */ class RepositoryUnreachableOrInvalid extends Exception { } /* * Class: Repository * Representation of an plugin repository. */ class Repository extends BySQLRowEnabled { private $id; private $baseurl; private $name; private $description; private $lastrefresh; private $stream_ctx; /* * Variables: Public class variables * $packages - Array with all packages from this repository. A entry itself is an array: array(name, versioncounter, description) */ public $packages; protected function __construct() { $this->stream_ctx = stream_context_create(array("http" => array("timeout" => 5))); } /* * Functions: Getters * get_id - Get internal ID. * get_baseurl - Get the baseurl of the repository. * get_name - Get repository name. * get_description - Get repository description. */ public function get_id() { return $this->id; } public function get_baseurl() { return $this->baseurl; } public function get_name() { return $this->name; } public function get_description() { return $this->description; } /* * Constructor: create * Create a new repository entry from a base url. * * Parameters: * $baseurl - The baseurl of the repository. * * Throws: * Could throw a <RepositoryUnreachableOrInvalid> exception. In this case, nothing will be written to the database. */ public static function create($baseurl) { $obj = new self(); if(preg_match('/^(http[s]?:\\/\\/.*?)[\\/]?$/', $baseurl, $matches) == 0) throw new RepositoryUnreachableOrInvalid(); $obj->baseurl = $matches[1]; $obj->refresh(True); transaction(function() use (&$obj) { global $db_con; qdb("INSERT INTO `ratatoeskr_repositories` () VALUES ()"); $obj->id = $db_con->lastInsertId(); $obj->save(); }); return $obj; } protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->name = $sqlrow["name"]; $this->description = $sqlrow["description"]; $this->baseurl = $sqlrow["baseurl"]; $this->packages = unserialize(base64_decode($sqlrow["pkgcache"])); $this->lastrefresh = $sqlrow["lastrefresh"]; } /* * Constructor: by_id * Get a repository entry by ID. * * Parameters: * $id - ID. * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `name`, `description`, `baseurl`, `pkgcache`, `lastrefresh` FROM `PREFIX_repositories` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if(!$sqlrow) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: all * Gets all available repositories. * * Returns: * Array of <Repository> objects. */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `name`, `description`, `baseurl`, `pkgcache`, `lastrefresh` FROM `PREFIX_repositories` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } private function save() { qdb("UPDATE `PREFIX_repositories` SET `baseurl` = ?, `name` = ?, `description` = ?, `pkgcache` = ?, `lastrefresh` = ? WHERE `id` = ?", $this->baseurl, $this->name, $this->description, base64_encode(serialize($this->packages)), $this->lastrefresh, $this->id); } /* * Function: delete * Delete the repository entry from the database. */ public function delete() { qdb("DELETE FROM `PREFIX_repositories` WHERE `id` = ?", $this->id); } /* * Function: refresh * Refresh the package cache and the name and description. * * Parameters: * $force - Force a refresh, even if the data was already fetched in the last 6 hours (default: False). * * Throws: * <RepositoryUnreachableOrInvalid> */ public function refresh($force = False) { if(($this->lastrefresh > (time() - (60*60*4))) and (!$force)) return; $repometa = @file_get_contents($this->baseurl . "/repometa", False, $this->stream_ctx); if($repometa === FALSE) throw new RepositoryUnreachableOrInvalid(); $repometa = @unserialize($repometa); if((!is_array($repometa)) or (!isset($repometa["name"])) or (!isset($repometa["description"]))) throw new RepositoryUnreachableOrInvalid(); $this->name = $repometa["name"]; $this->description = $repometa["description"]; $this->packages = @unserialize(@file_get_contents($this->baseurl . "/packagelist", False, $ctx)); $this->lastrefresh = time(); $this->save(); } /* * Function: get_package_meta * Get metadata of a plugin package from this repository. * * Parameters: * $pkgname - The name of the package. * * Throws: * A <DoesNotExistError> Exception, if the package was not found. * * Returns: * A <PluginPackageMeta> object */ public function get_package_meta($pkgname) { $found = False; foreach($this->packages as $p) { if($p[0] == $pkgname) { $found = True; break; } } if(!$found) throw new DoesNotExistError("Package not in package cache."); $pkgmeta = @unserialize(@file_get_contents($this->baseurl . "/packages/" . urlencode($pkgname) . "/meta", False, $this->stream_ctx)); if(!($pkgmeta instanceof PluginPackageMeta)) throw new DoesNotExistError(); return $pkgmeta; } /* * Function: download_package * Download a package from the repository * * Parameters: * $pkgname - Name of the package. * $version - The version to download (defaults to "current"). * * Throws: * * A <DoesNotExistError> Exception, if the package was not found. * * A <InvalidPackage> Exception, if the package was malformed. * * Returns: * A <PluginPackage> object. */ public function download_package($pkgname, $version = "current") { $found = False; foreach($this->packages as $p) { if($p[0] == $pkgname) { $found = True; break; } } if(!$found) throw new DoesNotExistError("Package not in package cache."); $raw = @file_get_contents($this->baseurl . "/packages/" . urlencode($pkgname) . "/versions/" . urlencode($version), False, $this->stream_ctx); if($raw === False) throw new DoesNotExistError(); return PluginPackage::load($raw); } } /* * Class: Article * Representation of an article */ class Article extends BySQLRowEnabled { private $id; private $section_id; private $section_obj; /* * Variables: Public class variables * * $urlname - URL name * $title - Title (an <Multilingual> object) * $text - The text (an <Multilingual> object) * $excerpt - Excerpt (an <Multilingual> object) * $meta - Keywords, comma seperated * $custom - Custom fields, is an array * $article_image - The article <Image>. If none: NULL * $status - One of the ARTICLE_STATUS_* constants * $timestamp - Timestamp * $allow_comments - Are comments allowed? */ public $urlname; public $title; public $text; public $excerpt; public $meta; public $custom; public $article_image; public $status; public $timestamp; public $allow_comments; protected function __construct() { $this->section_obj = NULL; } protected function populate_by_sqlrow($sqlrow) { $this->id = $sqlrow["id"]; $this->urlname = $sqlrow["urlname"]; $this->title = Multilingual::by_id($sqlrow["title"]); $this->text = Multilingual::by_id($sqlrow["text"]); $this->excerpt = Multilingual::by_id($sqlrow["excerpt"]); $this->meta = $sqlrow["meta"]; $this->custom = unserialize(base64_decode($sqlrow["custom"])); $this->article_image = $sqlrow["article_image"] == 0 ? NULL : Image::by_id($sqlrow["article_image"]); $this->status = $sqlrow["status"]; $this->section_id = $sqlrow["section"]; $this->timestamp = $sqlrow["timestamp"]; $this->allow_comments = $sqlrow["allow_comments"] == 1; } /* * Function: get_id */ public function get_id() { return $this->id; } /* * Function: test_urlname * Test, if a urlname is a valid urlname. * * Parameters: * $urlname - Urlname to test * * Returns: * True, if the urlname is valid, False otherwise. */ public static function test_urlname($urlname) { return (bool) preg_match('/^[a-zA-Z0-9-_]+$/', $urlname); } /* * Function: test_status * Test, if a status is valid. * * Parameters: * $status - Status value to test. * * Returns: * True, if the status is a valid status value, False otherwise. */ public static function test_status($status) { return is_numeric($status) and ($status >= 0) and ($status <= 3); } /* * Constructor: create * Create a new Article object. * * Parameters: * urlname - A unique URL name * * Throws: * <AlreadyExistsError>, <InvalidDataError> */ public static function create($urlname) { global $ratatoeskr_settings; global $db_con; if(!self::test_urlname($urlname)) throw new InvalidDataError("invalid_urlname"); try { self::by_urlname($urlname); } catch(DoesNotExistError $e) { $obj = new self(); $obj->urlname = $urlname; $obj->title = Multilingual::create(); $obj->text = Multilingual::create(); $obj->excerpt = Multilingual::create(); $obj->meta = ""; $obj->custom = array(); $obj->article_image = NULL; $obj->status = ARTICLE_STATUS_HIDDEN; $obj->section_id = $ratatoeskr_settings["default_section"]; $obj->timestamp = time(); $obj->allow_comments = $ratatoeskr_settings["allow_comments_default"]; qdb("INSERT INTO `PREFIX_articles` (`urlname`, `title`, `text`, `excerpt`, `meta`, `custom`, `article_image`, `status`, `section`, `timestamp`, `allow_comments`) VALUES ('', ?, ?, ?, '', ?, 0, ?, ?, ?, ?)", $obj->title->get_id(), $obj->text->get_id(), $obj->excerpt->get_id(), base64_encode(serialize($obj->custom)), $obj->status, $obj->section_id, $obj->timestamp, $obj->allow_comments ? 1 : 0); $obj->id = $db_con->lastInsertId(); return $obj; } throw new AlreadyExistsError(); } /* * Constructor: by_id * Get by ID. * * Parameters: * $id - The ID. * * Throws: * <DoesNotExistError> */ public static function by_id($id) { $stmt = qdb("SELECT `id`, `urlname`, `title`, `text`, `excerpt`, `meta`, `custom`, `article_image`, `status`, `section`, `timestamp`, `allow_comments` FROM `PREFIX_articles` WHERE `id` = ?", $id); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: by_urlname * Get by urlname * * Parameters: * $urlname - The urlname * * Throws: * <DoesNotExistError> */ public static function by_urlname($urlname) { $stmt = qdb("SELECT `id`, `urlname`, `title`, `text`, `excerpt`, `meta`, `custom`, `article_image`, `status`, `section`, `timestamp`, `allow_comments` FROM `PREFIX_articles` WHERE `urlname` = ?", $urlname); $sqlrow = $stmt->fetch(); if($sqlrow === False) throw new DoesNotExistError(); return self::by_sqlrow($sqlrow); } /* * Constructor: by_multi * Get Articles by multiple criterias * * Parameters: * $criterias - Array that can have these keys: id (int) , urlname (string), section (<Section> object), status (int), onlyvisible, langavail(string), tag (<Tag> object) * $sortby - Sort by this field (id, urlname, timestamp or title) * $sortdir - Sorting directory (ASC or DESC) * $count - How many entries (NULL for unlimited) * $offset - How many entries should be skipped (NULL for none) * $perpage - How many entries per page (NULL for no paging) * $page - Page number (starting at 1, NULL for no paging) * &$maxpage - Number of pages will be written here, if paging is activated. * * Returns: * Array of Article objects */ public static function by_multi($criterias, $sortby, $sortdir, $count, $offset, $perpage, $page, &$maxpage) { $subqueries = array(); $subparams = array(); foreach($criterias as $k => $v) { switch($k) { case "id": $subqueries[] = "`a`.`id` = ?"; $subparams[] = $v; break; case "urlname": $subqueries[] = "`a`.`urlname` = ?"; $subparams[] = $v; break; case "section": $subqueries[] = "`a`.`section` = ?"; $subparams[] = $v->get_id(); break; case "status": $subqueries[] = "`a`.`status` = ?"; $subparams[] = $v; break; case "onlyvisible": $subqueries[] = "`a`.`status` != 0"; break; case "langavail": $subqueries[] = "`b`.`language` = ?"; $subparams[] = $v; break; case "tag": $subqueries[] = "`c`.`tag` = ?"; $subparams[] = $v->get_id(); break; default: continue; } } if(($sortdir != "ASC") and ($sortdir != "DESC")) $sortdir = "ASC"; $sorting = ""; switch($sortby) { case "id": $sorting = "ORDER BY `a`.`id` $sortdir"; break; case "urlname": $sorting = "ORDER BY `a`.`urlname` $sortdir"; break; case "timestamp": $sorting = "ORDER BY `a`.`timestamp` $sortdir"; break; case "title": $sorting = "ORDER BY `b`.`text` $sortdir"; break; } $stmt = prep_stmt("SELECT `a`.`id` AS `id`, `a`.`urlname` AS `urlname`, `a`.`title` AS `title`, `a`.`text` AS `text`, `a`.`excerpt` AS `excerpt`, `a`.`meta` AS `meta`, `a`.`custom` AS `custom`, `a`.`article_image` AS `article_image`, `a`.`status` AS `status`, `a`.`section` AS `section`, `a`.`timestamp` AS `timestamp`, `a`.`allow_comments` AS `allow_comments` FROM `PREFIX_articles` `a` INNER JOIN `PREFIX_translations` `b` ON `a`.`title` = `b`.`multilingual` LEFT OUTER JOIN `PREFIX_article_tag_relations` `c` ON `a`.`id` = `c`.`article` WHERE " . implode(" AND ", $subqueries) . " $sorting"); $stmt->execute($subparams); $rows = array(); $fetched_ids = array(); while($sqlrow = $stmt->fetch()) { if(!in_array($sqlrow["id"], $fetched_ids)) { $rows[] = $sqlrow; $fetched_ids[] = $sqlrow["id"]; } } if($count !== NULL) $rows = array_slice($rows, 0, $count); if($offset !== NULL) $rows = array_slice($rows, $offset); if(($perpage !== NULL) and ($page !== NULL)) { $maxpage = ceil(count($rows) / $perpage); $rows = array_slice($rows, $perpage * ($page - 1), $perpage); } $rv = array(); foreach($rows as $r) $rv[] = self::by_sqlrow($r); return $rv; } /* * Constructor: all * Get all articles * * Returns: * Array of Article objects */ public static function all() { $rv = array(); $stmt = qdb("SELECT `id`, `urlname`, `title`, `text`, `excerpt`, `meta`, `custom`, `article_image`, `status`, `section`, `timestamp`, `allow_comments` FROM `PREFIX_articles` WHERE 1"); while($sqlrow = $stmt->fetch()) $rv[] = self::by_sqlrow($sqlrow); return $rv; } /* * Function: get_comments * Getting comments for this article. * * Parameters: * $limit_lang - Get only comments in a language (empty string for no limitation, this is the default). * $only_visible - Do you only want the visible comments? (Default: False) * * Returns: * Array of <Comment> objects. */ public function get_comments($limit_lang = "", $only_visible = False) { $rv = array(); $conditions = array("`article` = ?"); $arguments = array($this->id); if($limit_lang != "") { $conditions[] = "`language` = ?"; $arguments[] = $limit_lang; } if($only_visible) $conditions[] = "`visible` = 1"; $stmt = prep_stmt("SELECT `id`, `article`, `language`, `author_name`, `author_mail`, `text`, `timestamp`, `visible`, `read_by_admin` FROM `PREFIX_comments` WHERE " . implode(" AND ", $conditions)); $stmt->execute($arguments); while($sqlrow = $stmt->fetch()) $rv[] = Comment::by_sqlrow($sqlrow); return $rv; } /* * Function: get_tags * Get all Tags of this Article. * * Returns: * Array of <Tag> objects. */ public function get_tags() { $rv = array(); $stmt = qdb("SELECT `a`.`id` AS `id`, `a`.`name` AS `name`, `a`.`title` AS `title` FROM `PREFIX_tags` `a` INNER JOIN `PREFIX_article_tag_relations` `b` ON `a`.`id` = `b`.`tag` WHERE `b`.`article` = ?", $this->id); while($sqlrow = $stmt->fetch()) $rv[] = Tag::by_sqlrow($sqlrow); return $rv; } /* * Function: set_tags * Set the Tags that should be associated with this Article. * * Parameters: * $tags - Array of <Tag> objects. */ public function set_tags($tags) { transaction(function() use (&$tags) { foreach($tags as $tag) $tag->save(); qdb("DELETE FROM `PREFIX_article_tag_relations` WHERE `article`= ?", $this->id); $articleid = $this->id; if(!empty($tags)) { $stmt = prep_stmt( "INSERT INTO `PREFIX_article_tag_relations` (`article`, `tag`) VALUES " . implode(",", array_fill(0, count($tags), "(?,?)")) ); $args = array(); foreach($tags as $tag) { $args[] = $articleid; $args[] = $tag->get_id(); } $stmt->execute($args); } }); } /* * Function: get_section * Get the section of this article. * * Returns: * A <Section> object. */ public function get_section() { if($this->section_obj === NULL) $this->section_obj = Section::by_id($this->section_id); return $this->section_obj; } /* * Function: set_section * Set the section of this article. * * Parameters: * $section - A <Section> object. */ public function set_section($section) { $this->section_id = $section->get_id(); $this->section_obj = $section; } /* * Function: get_extradata * Get the extradata for this article and the given plugin. * * Parameters: * $plugin_id - The ID of the plugin. * * Returns: * An <ArticleExtradata> object. */ public function get_extradata($plugin_id) { return new ArticleExtradata($this->id, $plugin_id); } /* * Function: save * * Throws: * <AlreadyExistsError>, <InvalidDataError> */ public function save() { if(!self::test_urlname($this->urlname)) throw new InvalidDataError("invalid_urlname"); if(!self::test_status($this->status)) throw new InvalidDataError("invalid_article_status"); transaction(function() { $stmt = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_articles` WHERE `urlname` = ? AND `id` != ?", $this->urlname, $this->id); $sqlrow = $stmt->fetch(); if($sqlrow["n"] > 0) throw new AlreadyExistsError(); $this->title->save(); $this->text->save(); $this->excerpt->save(); qdb("UPDATE `PREFIX_articles` SET `urlname` = ?, `title` = ?, `text` = ?, `excerpt` = ?, `meta` = ?, `custom` = ?, `article_image` = ?, `status` = ?, `section` = ?, `timestamp` = ?, `allow_comments` = ? WHERE `id` = ?", $this->urlname, $this->title->get_id(), $this->text->get_id(), $this->excerpt->get_id(), $this->meta, base64_encode(serialize($this->custom)), $this->article_image === NULL ? 0 : $this->article_image->get_id(), $this->status, $this->section_id, $this->timestamp, $this->allow_comments ? 1 : 0, $this->id ); }); } /* * Function: delete */ public function delete() { transaction(function() { $this->title->delete(); $this->text->delete(); $this->excerpt->delete(); foreach($this->get_comments() as $comment) $comment->delete(); qdb("DELETE FROM `PREFIX_article_tag_relations` WHERE `article` = ?", $this->id); qdb("DELETE FROM `PREFIX_article_extradata` WHERE `article` = ?", $this->id); qdb("DELETE FROM `PREFIX_articles` WHERE `id` = ?", $this->id); }); } } /* * Class: ArticleExtradata * A Key-Value-Storage assigned to Articles for plugins to store additional data. * Can be accessed like an array. * Keys are strings and Values can be everything serialize() can process. * * Extends the abstract <KVStorage> class. */ class ArticleExtradata extends KVStorage { /* * Constructor: __construct * * Parameters: * $article_id - The ID of the Article. * $plugin_id - The ID of the Plugin. */ public function __construct($article_id, $plugin_id) { $this->init("PREFIX_article_extradata", array( "article" => $article_id, "plugin" => $plugin_id, )); } } /* * Function: dbversion * Get the version of the database structure currently used. * * Returns: * The numerical version of the current database structure. */ function dbversion() { /* Is the meta table present? If no, the version is 0. */ $stmt = qdb("SELECT COUNT(*) FROM `information_schema`.`tables` WHERE `table_schema` = ? AND `table_name` = ?", $config["mysql"]["db"], sub_prefix("PREFIX_meta")); list($n) = $stmt->fetch(); if($n == 0) return 0; $stmt = qdb("SELECT `value` FROM `PREFIX_meta` WHERE `key` = 'dbversion'"); $sqlrow = $stmt->fetch(); return unserialize(base64_decode($sqlrow["value"])); } /* * Function: clean_database * Clean up the database */ function clean_database() { global $ratatoeskr_settings; if((!isset($ratatoeskr_settings["last_db_cleanup"])) or ($ratatoeskr_settings["last_db_cleanup"] < (time() - 86400))) { Plugin::clean_db(); $ratatoeskr_settings["last_db_cleanup"] = time(); } } ?>