diff options
Diffstat (limited to 'ratatoeskr/sys')
-rw-r--r-- | ratatoeskr/sys/models.php | 343 | ||||
-rw-r--r-- | ratatoeskr/sys/plugin_api.php | 55 |
2 files changed, 330 insertions, 68 deletions
diff --git a/ratatoeskr/sys/models.php b/ratatoeskr/sys/models.php index fe80ce4..44367dd 100644 --- a/ratatoeskr/sys/models.php +++ b/ratatoeskr/sys/models.php @@ -74,6 +74,13 @@ class AlreadyExistsError extends Exception { } */ 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() { } @@ -89,6 +96,101 @@ abstract class BySQLRowEnabled } /* + * Class: KVStorage + * An abstract class for a KVStorage. + * + * See also: + * <PluginKVStorage>, <ArticleExtradata> + */ +abstract class KVStorage implements Countable, ArrayAccess, Iterator +{ + private $keybuffer; + private $counter; + private $prepared_queries; + private $silent_mode; + + final protected function init($sqltable, $common_fields) + { + $this->silent_mode = False; + $this->keybuffer = array(); + + $selector = "WHERE " . (empty($common_fields) ? 1 : implode(" AND ", array_map(function($x) { return qdb_fmt("`{$x[0]}` = {$x[1]}", $x[2]); }, $common_fields))); + $this->prepared_queries = array( + "get" => "SELECT `value` FROM `$sqltable` $selector AND `key` = '%s'", + "unset" => "DELETE FROM `$sqltable` $selector AND `key` = '%s'", + "update" => "UPDATE `$sqltable` SET `value` = '%s' $selector AND `key` = '%s'", + "create" => "INSERT INTO `$sqltable` (`key`, `value` " + . (empty($common_fields) ?: ", " . implode(", ", array_map(function($x) { return "`".$x[0]."`"; }, $common_fields))) + . ") VALUES ('%s', '%s'" + . (empty($common_fields) ?: ", " . implode(", ", array_map(function($x) { return qdb_fmt($x[1], $x[2]); }, $common_fields))) + . ")" + ); + + $result = qdb("SELECT `key` FROM `$sqltable` $selector"); + while($sqlrow = mysql_fetch_assoc($result)) + $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)) + { + $result = qdb($this->prepared_queries["get"], $offset); + $sqlrow = mysql_fetch_assoc($result); + 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); + qdb($this->prepared_queries["unset"], $offset); + } + } + final public function offsetSet($offset, $value) + { + if($this->offsetExists($offset)) + qdb($this->prepared_queries["update"], base64_encode(serialize($value)), $offset); + else + { + qdb($this->prepared_queries["create"], $offset, base64_encode(serialize($value))); + $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 */ @@ -771,13 +873,11 @@ $ratatoeskr_settings = Settings::get_instance(); * 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 implements Countable, ArrayAccess, Iterator +class PluginKVStorage extends KVStorage { - private $plugin_id; - private $keybuffer; - private $counter; - /* * Constructor: __construct * @@ -786,60 +886,10 @@ class PluginKVStorage implements Countable, ArrayAccess, Iterator */ public function __construct($plugin_id) { - $this->keybuffer = array(); - $this->plugin_id = $plugin_id; - - $result = qdb("SELECT `key` FROM `PREFIX_plugin_kvstorage` WHERE `plugin` = %d", $plugin_id); - while($sqlrow = mysql_fetch_assoc($result)) - $this->keybuffer[] = $sqlrow["key"]; - - $this->counter = 0; - } - - /* Countable interface implementation */ - public function count() { return count($this->keybuffer); } - - /* ArrayAccess interface implementation */ - public function offsetExists($offset) { return in_array($offset, $this->keybuffer); } - public function offsetGet($offset) - { - if($this->offsetExists($offset)) - { - $result = qdb("SELECT `value` FROM `PREFIX_plugin_kvstorage` WHERE `key` = '%s' AND `plugin` = %d", $offset, $this->plugin_id); - $sqlrow = mysql_fetch_assoc($result); - return unserialize(base64_decode($sqlrow["value"])); - } - else - throw new DoesNotExistError(); - } - public function offsetUnset($offset) - { - if($this->offsetExists($offset)) - { - unset($this->keybuffer[array_search($offset, $this->keybuffer)]); - $this->keybuffer = array_merge($this->keybuffer); - qdb("DELETE FROM `PREFIX_plugin_kvstorage` WHERE `key` = '%s' AND `plugin` = %d", $offset, $this->plugin_id); - } - } - public function offsetSet($offset, $value) - { - if($this->offsetExists($offset)) - qdb("UPDATE `PREFIX_plugin_kvstorage` SET `value` = '%s' WHERE `key` = '%s' AND `plugin` = %d", - base64_encode(serialize($value)), $offset, $this->plugin_id); - else - { - qdb("INSERT INTO `PREFIX_plugin_kvstorage` (`plugin`, `key`, `value`) VALUES (%d, '%s', '%s')", - $this->plugin_id, $offset, base64_encode(serialize($value))); - $this->keybuffer[] = $offset; - } + $this->init("PREFIX_plugin_kvstorage", array( + array("plugin", "%d", $plugin_id) + )); } - - /* Iterator interface implementation */ - function rewind() { return $this->position = 0; } - function current() { return $this->offsetGet($this->keybuffer[$this->position]); } - function key() { return $this->keybuffer[$this->position]; } - function next() { ++$this->position; } - function valid() { return isset($this->keybuffer[$this->position]); } } /* @@ -1080,6 +1130,21 @@ class Style extends BySQLRowEnabled } /* + * 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; } @@ -1096,6 +1161,9 @@ class Style extends BySQLRowEnabled */ public static function create($name) { + if(!self::test_name($name)) + throw new InvalidDataError("invalid_style_name"); + try { self::by_name($name); @@ -1181,6 +1249,9 @@ class Style extends BySQLRowEnabled */ public function save() { + if(!self::test_name($name)) + throw new InvalidDataError("invalid_style_name"); + $result = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_styles` WHERE `name` = '%s' AND `id` != %d", $this->name, $this->id); $sqlrow = mysql_fetch_assoc($result); if($sqlrow["n"] > 0) @@ -1372,6 +1443,7 @@ class Plugin extends BySQLRowEnabled { qdb("DELETE FROM `PREFIX_plugins` WHERE `id` = %d", $this->id); qdb("DELETE FROM `PREFIX_plugin_kvstorage` WHERE `plugin` = %d", $this->id); + qdb("DELETE FROM `PREFIX_article_extradata` WHERE `plugin` = %d", $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)) @@ -1421,6 +1493,21 @@ class Section extends BySQLRowEnabled } /* + * 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; } @@ -1433,10 +1520,13 @@ class Section extends BySQLRowEnabled * $name - The name of the new section. * * Throws: - * <AlreadyExistsError> + * <AlreadyExistsError>, <InvalidDataError> */ public static function create($name) { + if(!self::test_name($name)) + throw new InvalidDataError("invalid_section_name"); + try { $obj = self::by_name($name); @@ -1568,10 +1658,13 @@ class Section extends BySQLRowEnabled * Function: save * * Throws: - * <AlreadyExistsError> + * <AlreadyExistsError>, <InvalidDataError> */ public function save() { + if(!self::test_name($name)) + throw new InvalidDataError("invalid_section_name"); + $result = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_sections` WHERE `name` = '%s' AND `id` != %d", $this->name, $this->id); $sqlrow = mysql_fetch_assoc($result); if($sqlrow["n"] > 0) @@ -1627,6 +1720,21 @@ class Tag extends BySQLRowEnabled 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; } @@ -1646,10 +1754,13 @@ class Tag extends BySQLRowEnabled * $name - The name * * Throws: - * <AlreadyExistsError> + * <AlreadyExistsError>, <InvalidDataError> */ public static function create($name) { + if(!self::test_name($name)) + throw new InvalidDataError("invalid_tag_name"); + try { $obj = self::by_name($name); @@ -1763,10 +1874,13 @@ WHERE `b`.`tag` = '%d'" , $this->id); * Function: save * * Throws: - * <AlreadyExistsError> + * <AlreadyExistsError>, <InvalidDataError> */ public function save() { + if(!self::test_name($name)) + throw new InvalidDataError("invalid_tag_name"); + $result = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_tags` WHERE `name` = '%s' AND `id` != %d", $this->name, $this->id); $sqlrow = mysql_fetch_assoc($result); if($sqlrow["n"] > 0) @@ -2286,6 +2400,36 @@ class Article extends BySQLRowEnabled 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. * @@ -2293,12 +2437,15 @@ class Article extends BySQLRowEnabled * urlname - A unique URL name * * Throws: - * <AlreadyExistsError> + * <AlreadyExistsError>, <InvalidDataError> */ public static function create($urlname) { global $ratatoeskr_settings; + if(!self::test_urlname($urlname)) + throw new InvalidDataError("invalid_urlname"); + try { self::by_urlname($urlname); @@ -2562,10 +2709,34 @@ WHERE " . implode(" AND ", $subqueries) . " $sorting"); } /* + * 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"); + $result = qdb("SELECT COUNT(*) AS `n` FROM `PREFIX_articles` WHERE `urlname` = '%s' AND `id` != %d", $this->urlname, $this->id); $sqlrow = mysql_fetch_assoc($result); if($sqlrow["n"] > 0) @@ -2604,11 +2775,57 @@ WHERE " . implode(" AND ", $subqueries) . " $sorting"); $comment->delete(); qdb("DELETE FROM `PREFIX_article_tag_relations` WHERE `article` = %d", $this->id); + qdb("DELETE FROM `PREFIX_article_extradata` WHERE `article` = %d", $this->id); qdb("DELETE FROM `PREFIX_articles` WHERE `id` = %d", $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( + array("article", "%d", $article_id), + array("plugin", "%d", $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. */ + $result = qdb("SHOW TABLES LIKE 'PREFIX_meta'"); + if(mysql_num_rows($result) == 0) + return 0; + + $result = qdb("SELECT `value` FROM `PREFIX_meta` WHERE `key` = 'dbversion'"); + $sqlrow = mysql_fetch_assoc($result); + return unserialize(base64_decode($sqlrow["value"])); +} + +/* * Function: clean_database * Clean up the database */ diff --git a/ratatoeskr/sys/plugin_api.php b/ratatoeskr/sys/plugin_api.php index f6d1c2d..cd3e42b 100644 --- a/ratatoeskr/sys/plugin_api.php +++ b/ratatoeskr/sys/plugin_api.php @@ -15,17 +15,18 @@ require_once(dirname(__FILE__) . "/../frontend.php"); /* * Constant: APIVERSION - * The current API version (5). + * The current API version (6). */ -define("APIVERSION", 5); +define("APIVERSION", 6); /* * Array: $api_compat * Array of API versions, this version is compatible to (including itself). */ -$api_compat = array(3, 4, 5); +$api_compat = array(3, 4, 5, 6); + +$url_handlers = array(); /* master URL handler */ -$url_handlers = array(); /* * Function: register_url_handler * Register an URL handler. See <ratatoeskr/sys/urlprocess.php> for more details. @@ -42,6 +43,8 @@ function register_url_handler($name, $callback) $pluginpages_handlers = array(); +$articleeditor_plugins = array(); + /* * Class: RatatoeskrPlugin * An abstract class to be extended in order to write your own Plugin. @@ -93,7 +96,7 @@ abstract class RatatoeskrPlugin final protected function get_custompub_dir() { return SITE_BASE_PATH . "/ratatoeskr/plugin_extradata/public/" . $this->id; } final protected function get_custompub_url() { return $GLOBALS["rel_path_to_root"] . "/ratatoeskr/plugin_extradata/public/" . $this->id; } final protected function get_template_dir() { return "/plugintemplates/" . $this->id; } - + /* * Function: register_url_handler * Register a URL handler @@ -194,6 +197,33 @@ abstract class RatatoeskrPlugin } /* + * Function: register_articleeditor_plugin + * Register a plugin for the article editor in the backend. + * + * Parameters: + * $label - The label for the plugin. + * $fx - A function that will be called during the articleeditor. + * This function must accept these parameters: + * * $article - An <Article> object or NULL, if no Article is edited right now. + * * $about_to_save - If True, the article is about to be saved. + * If you want to veto the saving, return the rejection reason as a string. + * If everything is okay and you need to save additional data, return a callback function that accepts the saved <Article> object (that callback should also write data back to the template, if necessary). + * If everything is okay and you do not need to save additional data, return NULL. + * $template - The name of the template to display in the editor, relative to your template directory. If you do not want to display anything, you can set ths to NULL. + */ + final protected function register_articleeditor_plugin($label, $fx, $template) + { + global $articleeditor_plugins; + + $articleeditor_plugins[] = array( + "label" => $label, + "fx" => $fx, + "template" => $this->get_template_dir() . "/" . $template, + "display" => $template != NULL + ); + } + + /* * Function: get_backend_pluginpage_url * Get the URL to your backend plugin page. * @@ -207,6 +237,21 @@ abstract class RatatoeskrPlugin } /* + * Function: get_article_extradata + * Get the <ArticleExtradata> object for this plugin and the given article. + * + * Parameters: + * $article - An <Article> object. + * + * Returns: + * An <ArticleExtradata> object for this plugin and the given article. + */ + final protected function get_article_extradata($article) + { + return new ArticleExtradata($article->get_id(), $this->id); + } + + /* * Function: prepare_backend_pluginpage * Automatically sets the page title and highlights the menu-entry of your backend subpage. */ |