object to an array, so it can be accessed via a STE template. * * Parameters: * $section -
object. * $lang - The current language. * * Returns: * Array with these fields: * * id * * name * * title */ function section_transform_ste($section, $lang) { return array( "id" => $section->get_id(), "name" => $section->name, "title" => $section->title[$lang]->text ); } /* * Function: tag_transform_ste * Transforms an object to an array, so it can be accessed via a STE template. * * Parameters: * $section - object. * $lang - The current language. * * Returns: * Array with these fields: * * id * * name * * title */ function tag_transform_ste($tag, $lang) { return array( "id" => $tag->get_id(), "name" => $tag->name, "title" => $tag->title[$lang]->text ); } /* * Function: article_transform_ste * Transforms an
object to an array, so it can be accessed via a STE template. * * Parameters: * $article -
object. * $lang - The current language. * * Returns: * Array with these fields: * * id * * urlname * * fullurl * * title * * text * * excerpt * * custom (array: name=>value) * * status (numeric) * * section (sub-fields: ) * * timestamp, tags (array(sub-fields: )) * * languages (array: language name=>url) * * comments_allowed */ function article_transform_ste($article, $lang) { global $rel_path_to_root; $languages = array(); foreach($article->title as $language => $_) $languages[$language] = "$rel_path_to_root/$language/{$article->section->name}/{$article->urlname}"; return array( "id" => $article->get_id(), "urlname" => $article->urlname, "fullurl" => htmlesc("$rel_path_to_root/$lang/{$article->section->name}/{$article->urlname}"), "title" => htmlesc($article->title[$lang]->text), "text" => textprocessor_apply_translation($article->text[$lang]), "excerpt" => textprocessor_apply_translation($article->excerpt[$lang]), "custom" => $article->custom, "status" => $article->status, "section" => section_transform_ste($article->section, $lang), "timestamp" => $article->timestamp, "tags" => array_map(function($tag) use ($lang) { return tag_transform_ste($tag, $lang); }, $article->tags), "languages" => $languages, "comments_allowed" => $article->comments_allowed ); } function comment_filter($html) { return kses($html, 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: comment_transform_ste * Transforms an object to an array, so it can be accessed via a STE template. * * Parameters: * $comment - object. * * Returns: * Array with these fields: * * id * * text * * author * * timestamp */ function comment_transform_ste($comment) { global $rel_path_to_root, $ratatoeskr_settings; return array( "id" => $comment->get_id(), "text" => comment_filter(textprocessor_apply($comment->text, $ratatoeskr_settings["comment_textprocessor"])), "author" => htmlesc($comment->author_name), "timestamp" => $comment->get_timestamp() ); } /* Register some tags for the template engine */ /* * STETag: articles_get * Get articles by custom criterias. Will only get articles, that are available in the current language ($language). * The fields of an article can be looked up at . * * Parameters: * var - (mandatory) The name of the variable, where the current article should be stored at. * id - (optional) Filter by ID. * urlname - (optional) Filter by urlname. * section - (optional) Filter by section (section name). * status - (optional) Filter by status (numeric, ). * tag - (optional) Filter by tag (tag name). * sort - (optional) How to sort. Format: "fieldname direction" where fieldname is one of [id, urlname, title, timestamp] and direction is one of [asc, desc]. * perpage - (optional) How many articles should be shown per page (default unlimited). * page - (optional) On which page are we (starting with 1). Useful in combination with $current[page], and . (Default: 1) * maxpage - (optional) (variable name) If given, the number of pages are stored in this variable. * skip - (optional) How many articles should be skipped? (Default: none) * count - (optional) How many articles to output. (Default unlimited) * * Tag Content: * The tag's content will be executed for every article. The current article will be written to the variable specified by the var parameter before. * * Returns: * All results from the tag content. */ $ste->register_tag("articles_get", function($ste, $params, $sub) { $lang = $ste->vars["language"]; if(!isset($params["var"])) throw new Exception("Parameter var is needed in article_get!"); if(isset($params["section"])) { try { $params["section"] = Section::by_name($params["section"]); } catch(NotFoundError $e) { unset($params["section"]); } } $result = Article::by_multi($params); if(isset($params["tag"])) { if(!isset($result)) $result = Article::all(); $result = array_filter($result, function($article) use ($params) { return isset($article->tags[$params["tag"]]); }); } /* Now filter out the articles, where the current language does not exist */ $result = array_filter($result, function($article) use ($lang) { return isset($article->title[$lang]); }); /* Also filter the hidden ones out */ $result = array_filter($result, function($article) { return $article->status != ARTICLE_STATUS_HIDDEN; }); /* Convert articles to arrays */ $result = array_map(function($article) use ($lang) { return article_transform_ste($article, $lang); }, $result); function sort_fx_factory($cmpfx, $field, $direction) { return function($a, $b) use ($cmpfx, $field, $direction) { return $cmpfx($a[$field], $b[$field]) * $direction; }; } if(isset($params["sort"])) { list($field, $direction) = explode(" ", $params["sort"]); if((@$direction != "asc") and (@$direction != "desc")) $direction = "asc"; $direction = ($direction == "asc") ? 1 : -1; $sort_fx = NULL; switch($field) { case "id": $sort_fx = sort_fx_factory("intcmp", "id", $direction); break; case "urlname": $sort_fx = sort_fx_factory("strcmp", "urlname", $direction); break; case "title": $sort_fx = sort_fx_factory("strcmp", "title", $direction); break; case "timestamp": $sort_fx = sort_fx_factory("intcmp", "timestamp", $direction); break; } if($sort_fx !== NULL) usort($result, $sort_fx); } if(isset($params["perpage"])) { if(isset($params["maxpage"])) { $maxpage = ceil(count($result) / $params["perpage"]); $ste->set_var_by_name($params["maxpage"], $maxpage == 0 ? 1 : $maxpage); } $page = isset($params["page"]) ? $params["page"] : 1; $result = array_slice($result, ($page - 1) * $params["perpage"], $params["perpage"]); } else if(isset($params["skip"]) and isset($params["count"])) $result = array_slice($result, $params["skip"], $params["count"]); else if(isset($params["skip"])) $result = array_slice($result, $params["skip"]); else if(isset($params["count"])) $result = array_slice($result, 0, $params["count"]); $output = ""; foreach($result as $article) { $ste->set_var_by_name($params["var"], $article); $output .= $sub($ste); } return $output; }); /* * STETag: section_list * Iterate over all sections. * The fields of a section can be looked up at . * * Parameters: * var - (mandatory) The name of the variable, where the current section should be stored at. * exclude - (optional) Sections to exclude * include_default - (optional) Should the default section be included (default: No). * * Tag Content: * The tag's content will be executed for every section. The current section will be written to the variable specified by the var parameter before. * * Returns: * All results from the tag content. */ $ste->register_tag("section_list", function($ste, $params, $sub) { global $ratatoeskr_settings; $lang = $ste->vars["language"]; if(!isset($params["var"])) throw new Exception("Parameter var is needed in article_get!"); $result = Section::all(); if(isset($params["exclude"])) { $exclude = explode(",", $params["exclude"]); $result = array_filter($result, function($section) use ($exclude) { return !in_array($section->name, $exclude); }); } $result = array_filter($result, function($section) use ($default_section) { return $section->get_id() != $default_section; }); $result = array_map(function($section) use ($lang) { return section_transform_ste($section, $lang); }, $result); if($ste->evalbool($params["include_default"])) { $default = section_transform_ste(Section::by_id($ratatoeskr_settings["default_section"])); array_unshift($result, $default); } $output = ""; foreach($result as $section) { $ste->set_var_by_name($params["var"], $section); $output .= $sub($ste); } return $output; }); /* * STETag: article_comments_count * Get the number of comments for an article. * * Parameters: * article - (mandatory) The name of the variable, where the article is stored at. * * Returns: * The number of comments. */ $ste->register_tag("article_comments_count", function($ste, $params, $sub) { if(!isset($params["article"])) throw new Exception("Need parameter 'article' in ste:article_comments_count."); $tpl_article = $ste->get_var_by_name($params["article"]); $lang = $ste->vars["language"]; try { $article = Article::by_id(@$tpl_article["id"]); return count($article->get_comments($lang, True)); } catch(DoesNotExistError $e) { return 0; } }); /* * STETag: article_comments * List all comments for an article. * The fields of a comment can be looked up at . * * Parameters: * var - (mandatory) The name of the variable, where the current comment should be stored at. * article - (mandatory) The name of the variable, where the article is stored at. * sort - (optional) Should the comments be sorted chronologically (asc) or inverse (desc)? Default: asc * * Tag Content: * The tag's content will be executed for every comment. The current comment will be written to the variable specified by the var parameter before. * * Returns: * All results from the tag content. */ $ste->register_tag("article_comments", function($ste, $params, $sub) { if(!isset($params["var"])) throw new Exception("Need parameter 'var' in ste:article_comments."); if(!isset($params["article"])) throw new Exception("Need parameter 'article' in ste:article_comments."); $tpl_article = $ste->get_var_by_name($params["article"]); $lang = $ste->vars["language"]; try { $article = Article::by_id(@$tpl_article["id"]); } catch(DoesNotExistError $e) { return ""; } $comments = $article->get_comments($lang, True); $sortdir = (@$params["sort"] == "desc") ? -1 : 1; usort($comments, function($a,$b) use ($sortdir) { return intcmp($a->get_timestamp(), $b->get_timestamp()) * $sortdir; }); $comments = array_map("comment_transform_ste", $comments); $output = ""; foreach($comments as $comment) { $ste->set_var_by_name($params["var"], $comment); $output .= $sub($ste); } return $output; }); /* * STETag: comment_form * Generates a HTML form tag that allows the visitor to write a comment. * * Parameters: * article - (mandatory) The name of the variable, where the article is stored at. * default - (optional) If not empty, a default formular with the mandatory fields will be generated. * * Tag Content: * The tag's content will be written into the HTML form tag. * You have at least to define these fields: * * * - The Name of the author. * * - The E-Mailaddress of the author. * * - The Text of the comment. * * - Submit button. * * You might also want to define this: * * * - For a preview of the comment. * * If the parameter default is not empty, the tag's content will be thrown away. * * Returns: * The finished HTML form. * * See Also: * The "prevcomment" field in <$current>. */ $ste->register_tag("comment_form", function($ste, $params, $sub) { global $translation; if(!isset($params["article"])) throw new Exception("Need parameter 'article' in ste:comment_form."); $tpl_article = $ste->get_var_by_name($params["article"]); try { $article = Article::by_id($tpl_article["id"]); } catch (DoesNotExistError $e) { return ""; } if(!$article->allow_comments) return ""; /* A token protects from double sending of the same form and is also a simple protection from stupid spambots. */ $token = uniqid("", True); $_SESSION["ratatoeskr_comment_tokens"][$token] = time(); $form_header = "
"; if($ste->evalbool(@$params["default"])) $form_body = "

{$translation["comment_form_name"]}:

{$translation["comment_form_mail"]}:

{$translation["comment_form_text"]}:

"; else { $ste->vars["current"]["oldcomment"] = array( "name" => @$_POST["author_name"], "mail" => @$_POST["author_mail"], "text" => @$_POST["comment_text"] ); $form_body = $sub($ste); unset($ste->vars["current"]["oldcomment"]); } return "$form_header\n$form_body\n
"; }); /* * STETags: Page control * These tags can create links to the previous/next page. * * page_prev - Link to the previous page (if available). * page_next - Link to the next page (if available). * * Parameters: * current - (mandatory) The current page number. * maxpage - (mandatory) How many pages in total? * default - (optional) If not empty, a default localized link text will be used. * * Tag Content: * The tag's content will be used as the link text. * * Returns: * A Link to the previous / next page. */ $ste->register_tag("page_prev", function($ste, $params, $sub) { global $translation; if(!isset($params["current"])) throw new Exception("Need parameter 'current' in ste:page_prev."); if(!isset($params["maxpage"])) throw new Exception("Need parameter 'maxpage' in ste:page_prev."); if($params["current"] == 1) return ""; parse_str(parse_url($_SERVER["REQUEST_URI"], PHP_URL_QUERY), $query); $query["page"] = $params["current"] - 1; $url = $_SERVER["REDIRECT_URL"] . "?" . http_build_query($query); return "" . (($ste->evalbool(@$params["default"])) ? $translation["page_prev"] : $sub($ste)) . ""; }); $ste->register_tag("page_next", function($ste, $params, $sub) { global $translation; if(!isset($params["current"])) throw new Exception("Need parameter 'current' in ste:page_next."); if(!isset($params["maxpage"])) throw new Exception("Need parameter 'maxpage' in ste:page_next."); if($params["current"] == $params["maxpage"]) return ""; parse_str(parse_url($_SERVER["REQUEST_URI"], PHP_URL_QUERY), $query); $query["page"] = $params["current"] + 1; $url = $_SERVER["REDIRECT_URL"] . "?" . http_build_query($query); return "" . (($ste->evalbool(@$params["default"])) ? $translation["page_next"] : $sub($ste)) . ""; }); /* * STETag: languages * List all languages available in the current context. * * Parameters: * var - (mandatory) The name of the variable, where the current language information should be stored at. * * Sub-fields of var: * short - 2 letter code of language * fullname - The full name of the language * url - URL to the current page in this language * * Tag Content: * The tag's content will be executed for every language. The current language will be written to the variable specified by the var parameter before. * * Returns: * All results from the tag content. */ $ste->register_tag("languages", function($ste, $params, $sub) { global $languages, $ratatoeskr_settings, $rel_path_to_root; if(!isset($params["var"])) throw new Exception("Need parameter 'var' in ste:languages."); $langs = array(); if(isset($ste->vars["current"]["article"])) { try { $article = Article::by_id($ste->vars["current"]["article"]["id"]); foreach($article->title as $lang => $_) $langs[] = $lang; } catch(DoesNotExistError $e) {} } else { foreach($ratatoeskr_settings["languages"] as $lang) $langs[] = $lang; } $output = ""; foreach($langs as $lang) { $ste->set_var_by_name($params["var"], array( "short" => $lang, "fullname" => htmlesc($languages[$lang]["language"]), "url" => htmlesc("$rel_path_to_root/$lang/" . implode("/", array_slice($ste->vars["current"]["url_fragments"], 1))) )); $output .= $sub($ste); } return $output; }); /* * STETag: styles_load * Load all current styles. * * Parameters: * mode - (optional) Either "embed" or "link". Default: link * * Returns: * The current styles (either linked or embedded) */ $ste->register_tag("styles_load", function($ste, $params, $sub) { global $rel_path_to_root; if(isset($params["mode"]) and (($params["mode"] == "embed") or ($params["mode"] == "link"))) $mode = $params["mode"]; else $mode = "link"; if($mode == "embed") { $output = ""; foreach($ste->vars["current"]["styles"] as $stylename) { try { $style = Style::by_name($stylename); $output .= "/* Style: $stylename */\n" . $style->code . "\n"; } catch(DoesNotExistError $e) { $output .= "/* Warning: Failed to load style: $stylename */\n"; } } $output = ""; } else { $output = ""; foreach($ste->vars["current"]["styles"] as $stylename) { try { $style = Style::by_name($stylename); $output .= "\n"; } catch(DoesNotExistError $e) { $output .= "\n"; } } } return $output; }); $ste->register_tag("title", function($ste, $params, $sub) { $pagetitle = $sub($ste); if(isset($ste->vars["current"]["article"])) return $ste->vars["current"]["article"]["title"] . " – $pagetitle"; if(isset($ste->vars["current"]["section"])) return $ste->vars["current"]["section"]["title"] . " – $pagetitle"; return $pagetitle; }); /* * STEVar: $current * Holds information about the current page in the frontend (the part of the webpage, the visitor sees). * * $current has these fields: * * article - Only set if a single article is shown. Holds information about an article. (sub-fields are described at ). * * section - Only set if a whole section is shown. Holds information about an section. (sub-fields are described at ). * * tag - Only set if all articles with the same tag should be shown (pseudo section _tags). Holds information about a tag. (sub-fields are described at ). * * page - Which subpage is shown? Useful with , and the page parameter of . Default: 1 * * commented - True, if the visitor has successfully written a comment. * * comment_fail - If the user tried to comment, but the system rejected the comment, this will be set and will contain the error message. * * comment_prev - If the user wanted to preview the article, this will be set and contain the HTML code of the comment. * * styles - The styles, that should be loaded. You can also just use . * * url_fragments - Array of URL parts. Mainly used internally, so you *really* should not modify this one... * * oldcomment - The data of the previously sent formular (subfields: name, mail, text). Only set inside the tag. * * Plugins might insert their own $current fields. */ /* * STEVar: $language * The short form (e.g. "en" for English, "de" for German, ...) of the current language. */ $comment_validators = array( function() /* Basic validator. Checks, if all mandatory fields are set and if the form token is okay. Also provides some stupid spambot protection... */ { global $translation; if(empty($_POST["author_name"])) throw new CommentRejected($translation["author_name_missing"]); if(empty($_POST["author_mail"])) throw new CommentRejected($translation["author_email_missing"]); if(empty($_POST["comment_text"])) throw new CommentRejected($translation["comment_text_missing"]); if(!isset($_POST["comment_token"])) throw new CommentRejected($translation["comment_form_invalid"]); if(!isset($_SESSION["ratatoeskr_comment_tokens"][$_POST["comment_token"]])) throw new CommentRejected($translation["comment_form_invalid"]); if(time() - $_SESSION["ratatoeskr_comment_tokens"][$_POST["comment_token"]] < 10) /* Comment filled in in under 10 seconds? Most likely a spambot. */ throw new CommentRejected($translation["comment_too_fast"]); unset($_SESSION["ratatoeskr_comment_tokens"][$_POST["comment_token"]]); }, ); /* * Function: register_comment_validator * Register a comment validator. * * A comment validator is a function, that checks the $_POST fields and decides whether a comment should be stored or not (throws an exception with the rejection reason as the message). * * Parameters: * $fx - The validator function (function()). */ function register_comment_validator($fx) { global $comment_validators; $comment_validators[] = $fx; } /* * Function: frontend_url_handler */ function frontend_url_handler(&$data, $url_now, &$url_next) { global $ste, $ratatoeskr_settings, $languages, $metasections, $comment_validators; $path = array_merge(array($url_now), $url_next); $url_next = array(); $default_section = Section::by_id($ratatoeskr_settings["default_section"]); /* If no language or an invalid language was given, fix it. */ if((count($path) == 0) or (!isset($languages[$path[0]]))) { if(count($path > 0)) array_shift($path); array_unshift($path, $ratatoeskr_settings["default_language"]); } $ste->vars["current"]["url_fragments"] = $path; $lang = array_shift($path); $ste->vars["language"] = $lang; load_language($languages[$lang]["translation_exist"] ? $lang : "en"); /* English is always available */ if(count($path) == 0) array_unshift($path, $default_section->name); $section_name = array_shift($path); if($section_name == "_tags") { try { $tag = Tag::by_name(array_shift($path)); } catch(DoesNotExistError $e) { throw new NotFoundError(); } $ste->vars["current"]["tag"] = tag_transform_ste($tag, $lang); } else { try { $section = Section::by_name($section_name); } catch(DoesNotExistError $e) { throw new NotFoundError(); } if(count($path)== 0) $ste->vars["current"]["section"] = section_transform_ste($section, $lang); else { try { $article = Article::by_urlname(array_shift($path)); } catch(DoesNotExistError $e) { throw new NotFoundError(); } $ste->vars["current"]["article"] = article_transform_ste($article, $lang); if(isset($_GET["comment"])) { if(isset($_POST["preview_comment"])) $ste->vars["current"]["comment_prev"] = comment_filter(textprocessor_apply($_POST["comment_text"], $ratatoeskr_settings["comment_textprocessor"])); else if(isset($_POST["post_comment"])) { $rejected = False; try { foreach($comment_validators as $validator) call_user_func($validator); } catch(CommentRejected $e) { $ste->vars["current"]["comment_fail"] = htmlesc($e->getMessage()); $rejected = True; } if(!$rejected) { $comment = Comment::create($article, $lang); $comment->author_name = $_POST["author_name"]; $comment->author_mail = $_POST["author_email"]; $comment->text = $_POST["comment_text"]; $comment->save(); $ste->vars["current"]["commented"] = "Yes"; } } } } } $ste->vars["current"]["page"] = (isset($_GET["page"]) and is_numeric($_GET["page"])) ? $_GET["page"] : 1; if(!isset($section)) $section = $default_section; foreach($section->styles as $style) $ste->vars["current"]["styles"][] = $style->name; echo $ste->exectemplate("/usertemplates/" . $section->template); } /* * Class: CommentRejected * An Exeption a comment validator can throw, if the validation failed. * * See Also: * */ class CommentRejected extends Exception {} ?>