diff options
Diffstat (limited to 'ratatoeskr')
-rw-r--r-- | ratatoeskr/backend/main.php | 61 | ||||
-rw-r--r-- | ratatoeskr/cms_style/images/dead_emoticon.png | bin | 0 -> 230 bytes | |||
-rw-r--r-- | ratatoeskr/cms_style/layout.css | 156 | ||||
-rw-r--r-- | ratatoeskr/frontend.php | 748 | ||||
-rw-r--r-- | ratatoeskr/main.php | 14 | ||||
-rw-r--r-- | ratatoeskr/sys/default_settings.php | 3 | ||||
-rw-r--r-- | ratatoeskr/sys/textprocessors.php | 78 | ||||
-rw-r--r-- | ratatoeskr/templates/src/systemtemplates/content_write.html | 22 | ||||
-rw-r--r-- | ratatoeskr/templates/src/systemtemplates/error.html | 17 | ||||
-rwxr-xr-x | ratatoeskr/templates/src/systemtemplates/master.html | 61 | ||||
-rw-r--r-- | ratatoeskr/translations/en.php | 34 |
11 files changed, 1175 insertions, 19 deletions
diff --git a/ratatoeskr/backend/main.php b/ratatoeskr/backend/main.php index cd7bb35..9767cb4 100644 --- a/ratatoeskr/backend/main.php +++ b/ratatoeskr/backend/main.php @@ -12,18 +12,23 @@ require_once(dirname(__FILE__) . "/../sys/models.php"); require_once(dirname(__FILE__) . "/../sys/pwhash.php"); +$admin_grp = Group::by_name("admins"); + $backend_subactions = url_action_subactions(array( - "_default" => url_action_alias(array("login")), + "_index" => url_action_alias(array("login")), + "index" => url_action_alias(array("login")), + /* _prelude guarantees that the user is logged in properly, so we do not have to care about that later. */ "_prelude" => function(&$data, $url_now, &$url_next) { - global $ratatoeskr_settings; + global $ratatoeskr_settings, $admin_grp, $ste; + /* Check authentification */ - if(isset($_SESSION["uid"])) + if(isset($_SESSION["ratatoeskr_uid"])) { try { - $user = User::by_id($_SESSION["uid"]); - if($user->pwhash == $_SESSION["pwhash"]) + $user = User::by_id($_SESSION["ratatoeskr_uid"]); + if(($user->pwhash == $_SESSION["ratatoeskr_pwhash"]) and $user->member_of($admin_grp)) { if(empty($user->language)) { @@ -34,10 +39,12 @@ $backend_subactions = url_action_subactions(array( if($url_next[0] == "login") $url_next = array("content", "write"); + $data["user"] = $user; + $ste->vars["user"] = array("name" => $user->username); return; /* Authentification successful, continue */ } else - unset($_SESSION["uid"]); + unset($_SESSION["ratatoeskr_uid"]); } catch(DoesNotExistError $e) { @@ -48,10 +55,9 @@ $backend_subactions = url_action_subactions(array( /* If we are here, user is not logged in... */ $url_next = array("login"); }, - "index" => url_action_alias(array("login")), "login" => url_action_simple(function($data) { - global $ste; + global $ste, $admin_grp; if(!empty($_POST["user"])) { try @@ -59,24 +65,51 @@ $backend_subactions = url_action_subactions(array( $user = User::by_name($_POST["user"]); if(!PasswordHash::validate($_POST["password"], $user->pwhash)) throw new Exception(); - $_SESSION["uid"] = $user->get_id(); - $_SESSION["pwhash"] = $user->pwhash; + if(!$user->member_of($admin_grp)) + throw new Exception(); + $_SESSION["ratatoeskr_uid"] = $user->get_id(); + $_SESSION["ratatoeskr_pwhash"] = $user->pwhash; } catch(Exception $e) { $ste->vars["login_failed"] = True; } - /* Login successful. Now redirect... */ + /* Login successful. */ + $data["user"] = $user; + $ste->vars["user"] = array("name" => $user->username); throw new Redirect(array("content", "write")); } echo $ste->exectemplate("systemtemplates/backend_login.html"); }), - "content" => url_action_simple(function($data) + "logout" => url_action_simple(function($data) { - print "hi"; - }) + echo "foo"; + unset($_SESSION["ratatoeskr_uid"]); + unset($_SESSION["ratatoeskr_pwhash"]); + throw new Redirect(array("login")); + }), + "content" => url_action_subactions(array( + "write" => function(&$data, $url_now, &$url_next) + { + global $ste, $translation; + + $article = array_slice($url_next, 0); + $url_next = array(); + + $ste->vars["section"] = "content"; + $ste->vars["submenu"] = "newarticle"; + + if(empty($article)) + { + /* New Article */ + $ste->vars["pagetitle"] = $translation["new_article"]; + } + + echo $ste->exectemplate("systemtemplates/content_write.html"); + } + )) )); ?> diff --git a/ratatoeskr/cms_style/images/dead_emoticon.png b/ratatoeskr/cms_style/images/dead_emoticon.png Binary files differnew file mode 100644 index 0000000..1a07509 --- /dev/null +++ b/ratatoeskr/cms_style/images/dead_emoticon.png diff --git a/ratatoeskr/cms_style/layout.css b/ratatoeskr/cms_style/layout.css new file mode 100644 index 0000000..42ea1bc --- /dev/null +++ b/ratatoeskr/cms_style/layout.css @@ -0,0 +1,156 @@ +* { + font-family: sans-serif; + font-size: 10pt; +} + +body, html { + margin: 0px; + padding:0px; +} + +#bar_top { + margin: 0px; + position: fixed; + top: 0px; + left: 0px; + width: 100%; + background: black; + padding: 3px 15px 3px; + color: #ddd; + z-index: 1000; +} +#bar_top .user { + text-align: right; + position: absolute; + right: 45px; +} + +#bar_top strong { + color: white; +} + +#bar_top a { + color: #ddd; + text-decoration: underline; +} + +#bar_top a:hover { + color: #fff; +} + +#maincontainer { + position: absolute; + width: 95%; + left: 2.5%; + margin: 2.5em 0em 2em; + padding: 0px; +} + +#mainmenu { + float:left; + width: 55mm +} + +#mainmenu, #mainmenu ul { + padding: 0px; + margin: 0px; + list-style: none; +} + +#mainmenu ul { + display: none; +} + +#mainmenu ul.active { + display: block; +} + +#mainmenu li { + font-size: 12pt; +} + +#mainmenu li a { + padding: 0.5ex 2mm 0.5ex 2mm; + border-top: 1px solid #eee; + font-size: 12pt; + color: #666; + text-decoration: none; + display:block; +} + +#mainmenu li a.first { + border: none; +} + +#mainmenu ul li a { + padding: 0.5ex 0px 0.5ex 7mm; +} + +#mainmenu li a.active { + color: black; +} + +#mainmenu li a:hover { + color: black; + background: #eee; +} + +#content { + padding: 0mm 0mm 0mm 60mm; +} + +h1 { + font-size: 14pt; + font-weight: normal; + margin: 0mm auto 1.5em; + color: #333; + text-align: center; + padding: 0px 0px 5px 0px; + border-bottom: 1px solid #eee; + /*background: url(images/hline.png) no-repeat bottom center;*/ + min-width: 300px; +} + +code, code pre { + font-family: monospace; + background: #eee; +} + +h1 strong { + font-size: 14pt; + font-weight: bold; + color: black; +} + +h2 { + font-size: 11pt; + font-weight: bold; +} + +.triplecolumns .column_left{ + float: left; + width: 50mm; + margin: 0px; + padding: 0px; +} + +.triplecolumns .column_right { + float: right; + width: 50mm; + margin: 0px; + padding: 0px; +} + +.triplecolumns .column_main { + margin: 0mm; + padding: 0mm 55mm 0mm; +} + +.triplecolumns_stop { + clear: both; +} + +form.fullwidthinputs input[type="text"], form.fullwidthinputs input[type="password"], form.fullwidthinputs select, form.fullwidthinputs textarea { + width: 100%; + margin:0mm; +} diff --git a/ratatoeskr/frontend.php b/ratatoeskr/frontend.php new file mode 100644 index 0000000..eff7511 --- /dev/null +++ b/ratatoeskr/frontend.php @@ -0,0 +1,748 @@ +<?php +/* + * File: ratatoeskr/frontend.php + * All the stuff for the frontend (i.e. what the visitor of the website sees). + * + * 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__) . "/sys/utils.php"); +require_once(dirname(__FILE__) . "/languages.php"); +require_once(dirname(__FILE__) . "/sys/models.php"); +require_once(dirname(__FILE__) . "/sys/textprocessors.php"); + +/* + * Function: section_transform_ste + * Transforms an <Section> object to an array, so it can be accessed via a STE template. + * + * Parameters: + * $section - <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 <Tag> object to an array, so it can be accessed via a STE template. + * + * Parameters: + * $section - <Tag> 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 <Article> object to an array, so it can be accessed via a STE template. + * + * Parameters: + * $article - <Article> object. + * $lang - The current language. + * + * Returns: + * Array with these fields: + * * id + * * urltitle + * * fullurl + * * title + * * text + * * excerpt + * * custom (array: name=>value) + * * status (numeric) + * * section (sub-fields: <section_transform_ste>) + * * timestamp, tags (array(sub-fields: <tag_transform_ste>)) + * * 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->urltitle}"; + + return array( + "id" => $article->get_id(), + "urltitle" => $article->urltitle, + "fullurl" => htmlesc("$rel_path_to_root/$lang/{$article->section->name}/{$article->urltitle}"), + "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_transform_ste + * Transforms an <Comment> object to an array, so it can be accessed via a STE template. + * + * Parameters: + * $comment - <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" => 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 <article_transform_ste>. + * + * Parameters: + * var - (mandatory) The name of the variable, where the current article should be stored at. + * id - (optional) Filter by ID. + * urltitle - (optional) Filter by urltitle. + * section - (optional) Filter by section (section name). + * status - (optional) Filter by status (numeric, <ARTICLE_STATUS_>). + * tag - (optional) Filter by tag (tag name). + * sort - (optional) How to sort. Format: "fieldname direction" where fieldname is one of [id, urltitle, 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], <page_prev> and <page_next>. (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!"); + + $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 ($sorter, $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 "urltitle": $sort_fx = sort_fx_factory("strcmp", "urltitle", $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"])) + $ste->set_var_by_name($params["maxpage"], ceil(count($result) / $params["perpage"])); + $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 <section_transform_ste>. + * + * 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 <comment_transform_ste>. + * + * 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. + * + * 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); + usort($comments, function($a,$b) { intcmp($a->get_timestamp(), $b->get_timestamp()); }); + + $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: + * + * * <input type="text" name="author_name" /> - The Name of the author. + * * <input type="text" name="author_mail" /> - The E-Mailaddress of the author. + * * <textarea name="comment_text"></textarea> - The Text of the comment. + * * <input type="submit" name="post_comment" /> - Submit button. + * + * You might also want to define this: + * + * * <input type="submit" name="preview_comment" /> - 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. + */ +$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 ""; + + $form_header = "<form action=\"{$tpl_article["fullurl"]}?comment\" method=\"POST\" accept-charset=\"UTF-8\">"; + + if($ste->evalbool(@$params["default"])) + $form_body = "<p>{$translation["comment_form_name"]}: <input type=\"text\" name=\"author_name\" /></p> +<p>{$translation["comment_form_mail"]}: <input type=\"text\" name=\"author_mail\" /></p> +<p>{$translation["comment_form_text"]}:<br /><textarea name=\"comment_text\" cols=\"50\" rows=\"10\"></textarea></p> +<p><input type=\"submit\" name=\"post_comment\" /></p>"; + else + $form_body = $sub($ste); + + return "$form_header\n$form_body\n</form>"; +}); + +/* + * 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) +{ + 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["page"] == 1) + return ""; + + parse_str(parse_url($_SERVER["REQUEST_URI"], PHP_URL_QUERY), $query); + $query["page"] = $params["page"] - 1; + $url = $_SERVER["REDIRECT_URL"] . "?" . http_build_query($query); + return "<a href=\"" . htmlesc($url) . "\">" . (($ste->evalbool(@$params["default"])) ? $translation["page_prev"] : $sub($ste)) . "</a>"; +}); + +$ste->register_tag("page_next", function($ste, $params, $sub) +{ + 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["page"] == $params["maxpage"]) + return ""; + + parse_str(parse_url($_SERVER["REQUEST_URI"], PHP_URL_QUERY), $query); + $query["page"] = $params["page"] + 1; + $url = $_SERVER["REDIRECT_URL"] . "?" . http_build_query($query); + return "<a href=\"" . htmlesc($url) . "\">" . (($ste->evalbool(@$params["default"])) ? $translation["page_next"] : $sub($ste)) . "</a>"; +}); + +/* + * 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" => urlesc($languages[$lang]["language"]), + "url" => urlesc("$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 = "<style type=\"text/css\">\n" . htmlesc($output) . "</style>"; + } + else + { + $output = ""; + foreach($ste->vars["current"]["styles"] as $stylename) + { + try + { + $style = Style::by_name($stylename); + $output .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . htmlesc($rel_path_to_root . "css.php?name=" . urlencode($style->name)) . "\" />\n"; + } + catch(DoesNotExistError $e) + { + $output .= "<!-- Warning: Failed to load style: $stylename */ -->\n"; + } + } + } + return $output; +}); + +/* + * 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 <article_transform_ste>). + * * section - Only set if a whole section is shown. Holds information about an section. (sub-fields are described at <section_transform_ste>). + * * tag - Only set if all articles with the same tag should be shown. Holds information about a tag. (sub-fields are described at <tag_transform_ste>). + * * page - Which subpage is shown? Useful with <page_prev>, <page_next> and the page parameter of <articles_get>. 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 <styles_load>. + * * url_fragments - Array of URL parts. Mainly used internally, so you *really* should not modify this one... + * + * 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() + { + global $translation; + if(empty($_POST["author_name"])) + throw new CommentRejected($translation["author_name_missing"]); + if(empty($_POST["author_email"])) + throw new CommentRejected($translation["author_email_missing"]); + if(empty($_POST["comment_text"])) + throw new CommentRejected($translation["comment_text_missing"]); + } +); + +/* + * 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 <CommentRejected> 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, $ratatoeskr_settings["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); + } + 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); + else + { + try + { + $article = Article::by_urlname(array_shift($path)); + } + catch(DoesNotExistError $e) + { + throw new NotFoundError(); + } + $ste->vars["current"]["article"] = article_transform_ste($article); + + if(isset($_GET["comment"])) + { + if(isset($_POST["comment_prev"])) + $ste->vars["current"]["comment_prev"] = 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: + * <register_comment_validator> + */ +class CommentValidationFailed extends Exception {} + +?> diff --git a/ratatoeskr/main.php b/ratatoeskr/main.php index 09f4d8d..041b96e 100644 --- a/ratatoeskr/main.php +++ b/ratatoeskr/main.php @@ -15,7 +15,7 @@ require_once(dirname(__FILE__) . "/sys/models.php"); require_once(dirname(__FILE__) . "/sys/init_ste.php"); require_once(dirname(__FILE__) . "/sys/translation.php"); require_once(dirname(__FILE__) . "/sys/urlprocess.php"); -#require_once(dirname(__FILE__) . "/frontend.php"); +require_once(dirname(__FILE__) . "/frontend.php"); require_once(dirname(__FILE__) . "/backend/main.php"); function ratatoeskr() @@ -38,12 +38,20 @@ function ratatoeskr() } /* Register URL handlers */ - #register_url_handler("_default", "frontend_url_handler"); + register_url_handler("_default", "frontend_url_handler"); register_url_handler("backend", $backend_subactions); - #register_url_handler("_notfound", "e404handler"); + register_url_handler("_notfound", url_action_simple(function($data) + { + global $ste; + //header("HTTP/1.1 404 Not Found"); + $ste->vars["title"] = "404 Not Found"; + $ste->vars["details"] = str_replace("[[URL]]", $_SERVER["REQUEST_URI"], (isset($translation) ? $translation["e404_details"] : "The page [[URL]] could not be found. Sorry.")); + echo $ste->exectemplate("systemtemplates/error.html"); + })); $urlpath = explode("/", $_GET["action"]); $rel_path_to_root = implode("/", array_merge(array("."), array_repeat("..", count($urlpath) - 1))); + $GLOBALS["rel_path_to_root"] = $rel_path_to_root; $data = array("rel_path_to_root" => $rel_path_to_root); $ste->vars["rel_path_to_root"] = $rel_path_to_root; diff --git a/ratatoeskr/sys/default_settings.php b/ratatoeskr/sys/default_settings.php index 7b42028..cb755cb 100644 --- a/ratatoeskr/sys/default_settings.php +++ b/ratatoeskr/sys/default_settings.php @@ -4,7 +4,8 @@ $default_settings = array( "comment_visible_default" => False, "default_language" => "en", "default_section" => 0/* Must be created */ - "allow_comments_default" => True + "allow_comments_default" => True, + "comment_textprocessor" => "Markdown" ); ?> diff --git a/ratatoeskr/sys/textprocessors.php b/ratatoeskr/sys/textprocessors.php new file mode 100644 index 0000000..3e67b01 --- /dev/null +++ b/ratatoeskr/sys/textprocessors.php @@ -0,0 +1,78 @@ +<?php +/* + * File: ratatoeskr/textprocessors.php + * Manage text processors (functions that transform text to HTML) and implement some default ones. + * + * 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__) . "/../libs/markdown.php"); +require_once(dirname(__FILE__) . "/utils.php"); + +/* + * Function: textprocessor_register + * Register a textprocessor. + * + * Parameters: + * $namen - The name of the textprocessor + * $fx - The textprocessor function (function($input), returns HTML) + * $visible_in_backend - Should this textprocessor be visible in the backend? Defaults to True. + */ +function textprocessor_register($name, $fx, $visible_in_backend=True) +{ + global $textprocessors; + $textprocessors[$name] = array($fx, $visible_in_backend); +} + +/* + * Function: textprocessor_apply + * Apply a textprocessor on a text. + * + * Parameters: + * $text - The input text. + * $textprocessor - The name of the textprocessor. + * + * Returns: + * HTML + */ +function textprocessor_apply($text, $textprocessor) +{ + global $textprocessors; + if(!isset($textprocessors[$textprocessor])) + throw new Exception("Unknown Textprocessor: $textprocessor"); + + $fx = @$textprocessors[$textprocessor][0]; + if(!is_callable($fx)) + throw new Exception("Invalid Textprocessor: $textprocessor"); + + return call_user_func($fx, $text); +} + +/* + * Function: textprocessor_apply_translation + * Applys a textprocessor automatically on a <Translation> object. The used textprocessor is determined by the $texttype property. + * + * Parameters: + * $translationobj - The <Translation> object. + * + * Returns: + * HTML + */ +function textprocessor_apply_translation($translationobj) +{ + return textprocessor_apply($translationobj->text, $translationobj->texttype); +} + +if(!isset($textprocessors)) +{ + $textprocessors = array( + "Markdown" => array("Markdown", True), + "Plain Text" => array(function($text) { return str_replace(array("\r\n", "\n"), array("<br />", "<br />"), htmlesc($text)); }, True), + "HTML" => array(function($text) { return $text; }, True) + ); +} + +?> diff --git a/ratatoeskr/templates/src/systemtemplates/content_write.html b/ratatoeskr/templates/src/systemtemplates/content_write.html new file mode 100644 index 0000000..962f116 --- /dev/null +++ b/ratatoeskr/templates/src/systemtemplates/content_write.html @@ -0,0 +1,22 @@ +<ste:load name="master.html" /> +<ste:block name="content"> + <form action="$rel_path_to_root/content/write?{$article_editurl|/$article_editurl|}" method="POST" accept_charset="utf-8" class="fullwidthinputs"> + <div class="triplecolumns"> + <div class="column_left"> + <h2>Markdown Cheat Sheet</h2> + </div> + <div class="column_right"> + <h2>Settings / Metadata</h2> + + <p>Unique URL Title: <input type="text" name="urltitle" value="<ste:escape>$urltitle</ste:escape>" /></p> + <p></p> + </div> + <div class="column_main"> + <p><ste:get_translation for="articleedit_title" />: <input type="text" name="title" value="<ste:escape>$title</ste:escape>" /></p> + <p><ste:get_translation for="articleedit_content" />: <textarea name="content" cols="80" rows="20"><ste:escape>$content</ste:escape></textarea></p> + <p><ste:get_translation for="articleedit_excerpt" />: <textarea name="excerpt" cols="80" rows="10"><ste:escape>$excerpt</ste:escape></textarea></p> + </div> + </div> + <div class="triplecolumns_stop"></div> + </form> +</ste:block> diff --git a/ratatoeskr/templates/src/systemtemplates/error.html b/ratatoeskr/templates/src/systemtemplates/error.html new file mode 100644 index 0000000..c8ece93 --- /dev/null +++ b/ratatoeskr/templates/src/systemtemplates/error.html @@ -0,0 +1,17 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta http-equiv="Content-Style-Type" content="text/css" /> + <title>$title</title> + <style type="text/css" media="screen"> + * { font-family: sans-serif; color: #bfbfbf; text-align: center;} + body { background: #3f3f3f; } + </style> +</head> +<body> + <h1><ste:escape>$title</ste:escape></h1> + <img src="$rel_path_to_root/ratatoeskr/cms_style/images/dead_emoticon.png" alt="" /> + <p><ste:escape>$details</ste:escape></p> +</body> +</html> diff --git a/ratatoeskr/templates/src/systemtemplates/master.html b/ratatoeskr/templates/src/systemtemplates/master.html new file mode 100755 index 0000000..ca7175f --- /dev/null +++ b/ratatoeskr/templates/src/systemtemplates/master.html @@ -0,0 +1,61 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta http-equiv="Content-Style-Type" content="text/css" /> + <title><ste:get_translation for="section_$section" />::<ste:escape>$pagetitle</ste:escape> - Ratatöskr</title> + <ste:mktag name="cms_style"><link rel="stylesheet" type="text/css" media="screen" href="$rel_path_to_root/ratatoeskr/cms_style/<ste:tagcontent />" /></ste:mktag> + <ste:cms_style>layout.css</ste:cms_style> + <ste:foreach array="additional_styles" value="cssfile"><ste:cms_style>$cssfile</ste:cms_style></ste:foreach> +</head> +<body> + <div id="bar_top"> + <span class="branding"><strong>Ratatöskr</strong>::Noctilucent clouds (v. 0.1)</span> + <span class="user"> + <a href="$rel_path_to_root/backend/admin/users/_self">$user[name]</a> | <a href="$rel_path_to_root/backend/logout"><ste:get_translation for="logout" /></a> + </span> + </div> + <div id="maincontainer"> + <h1><strong><ste:get_translation for="section_$section" /></strong>::<ste:escape>$pagetitle</ste:escape></h1> + <ul id="mainmenu"> + <li> + <a href="$rel_path_to_root/backend/content/write" class="first"><ste:get_translation for="section_content" /></a> + <ul?{~{content|eq|$section}| class="active"|}> + <li><a href="$rel_path_to_root/backend/content/articles"?{~{articles|eq|$submenu}| class="active"|}><ste:get_translation for="menu_articles" /></a></li> + <li><a href="$rel_path_to_root/backend/content/write"?{~{newarticle|eq|$submenu}| class="active"|}><ste:get_translation for="menu_newarticles" /></a></li> + <li><a href="$rel_path_to_root/backend/content/tags"?{~{tags|eq|$submenu}| class="active"|}><ste:get_translation for="menu_tags" /></a></li> + <li><a href="$rel_path_to_root/backend/content/images"?{~{images|eq|$submenu}| class="active"|}><ste:get_translation for="menu_images" /></a></li> + <li><a href="$rel_path_to_root/backend/content/comments"?{~{comments|eq|$submenu}| class="active"|}><ste:get_translation for="menu_comments" /></a></li> + </ul> + </li> + <li> + <a href="$rel_path_to_root/backend/design/templates"><ste:get_translation for="section_design" /></a> + <ul?{~{design|eq|$section}| class="active"|}> + <li><a href="$rel_path_to_root/backend/design/sections"?{~{sections|eq|$submenu}| class="active"|}><ste:get_translation for="menu_pagesections" /></a></li> + <li><a href="$rel_path_to_root/backend/design/templates"?{~{templates|eq|$submenu}| class="active"|}><ste:get_translation for="menu_templates" /></a></li> + <li><a href="$rel_path_to_root/backend/design/styles"?{~{styles|eq|$submenu}| class="active"|}><ste:get_translation for="menu_styles" /></a></li> + </ul> + </li> + <li> + <a href="$rel_path_to_root/backend/admin/settings"><ste:get_translation for="section_admin" /></a> + <ul?{~{admin|eq|$section}| class="active"|}> + <li><a href="$rel_path_to_root/backend/admin/users"?{~{users|eq|$submenu}| class="active"|}><ste:get_translation for="menu_users_groups" /></a></li> + <li><a href="$rel_path_to_root/backend/admin/languages"?{~{comments|eq|$submenu}| class="active"|}><ste:get_translation for="menu_languages" /></a></li> + <li><a href="$rel_path_to_root/backend/admin/repos"?{~{repos|eq|$submenu}| class="active"|}><ste:get_translation for="menu_plugin_repos" /></a></li> + <li><a href="$rel_path_to_root/backend/admin/settings"?{~{settings|eq|$submenu}| class="active"|}><ste:get_translation for="menu_settings" /></a></li> + </ul> + </li> + <li> + <a href="$rel_path_to_root/backend/plugin/list"><ste:get_translation for="section_plugins" /></a> + <ul?{~{plugins|eq|$section}| class="active"|}> + <li><a href="$rel_path_to_root/backend/plugin/list"?{~{pluginlist|eq|$submenu}| class="active"|}><ste:get_translation for="menu_pluginlist" /></a></li> + <li><a href="$rel_path_to_root/backend/plugin/install"?{~{installplugins|eq|$submenu}| class="active"|}><ste:get_translation for="menu_plugininstall" /></a></li> + </ul> + </li> + </ul> + <div id="content"> + <ste:block name="content"></ste:block> + </div> + </div> +</body> +</html> diff --git a/ratatoeskr/translations/en.php b/ratatoeskr/translations/en.php index 20c87b9..93951b4 100644 --- a/ratatoeskr/translations/en.php +++ b/ratatoeskr/translations/en.php @@ -6,7 +6,39 @@ $translation = array( "login_form_header" => "Login", "login_form_button" => "Login", "login_background_image" => "Background image: <a href=\"[[URL]]\">[[FILENAME]]</a> by [[AUTHOR]]. License: [[LICENSE]]", - "login_failed" => "Login failed." + "login_failed" => "Login failed (Username/Password is wrong or you are not an admin).", + "logout" => "Logout", + "section_admin" => "Administration", + "section_content" => "Content", + "section_design" => "Design", + "section_plugins" => "Plugins", + "menu_articles" => "Articles", + "menu_comments" => "Comments", + "menu_images" => "Images", + "menu_languages" => "Languages", + "menu_newarticles" => "New Article", + "menu_pagesections" => "Page Sections", + "menu_plugin_repos" => "Plugin Repositories", + "menu_plugininstall" => "Install Plugin", + "menu_pluginlist" => "Plugin List", + "menu_settings" => "Settings", + "menu_styles" => "Styles", + "menu_tags" => "Tags", + "menu_templates" => "Templates", + "menu_users_groups" => "Users / Groups", + "new_article" => "New Article", + "articleedit_title" => "Article Title", + "articleedit_content" => "Content", + "articleedit_excerpt" => "Excerpt", + "comment_form_name" => "Your name", + "comment_form_mail" => "Your E-Mailaddress", + "comment_form_text" => "Your comment (Markdown format)", + "page_prev" => "<-- previous page", + "page_next" => "next page -->", + "e404_details" => "The page [[URL]] could not be found. Sorry.", + "author_name_missing" => "No author name given.", + "author_email_missing" => "No author E-Mailaddress given.", + "comment_text_missing" => "An empty comment can not be posted." ); ?> |