aboutsummaryrefslogtreecommitdiff
path: root/ratatoeskr
diff options
context:
space:
mode:
authorKevin Chabowski <kevin@kch42.de>2011-10-05 14:36:01 +0200
committerKevin Chabowski <kevin@kch42.de>2011-10-05 14:36:01 +0200
commit0f185543bb9851fddc137f81a1e2a1d21589bc83 (patch)
treec9a3c56ba2166defac508f8d1c67ef81fe4e9789 /ratatoeskr
parentafe436b25dd935aa2ae3a327027ea04b3c82e5ac (diff)
downloadratatoeskr-cms-0f185543bb9851fddc137f81a1e2a1d21589bc83.tar.gz
ratatoeskr-cms-0f185543bb9851fddc137f81a1e2a1d21589bc83.tar.bz2
ratatoeskr-cms-0f185543bb9851fddc137f81a1e2a1d21589bc83.zip
Backend, frontend and 404 handlers partially implemented.
* Backend layout done. * Frontend theoretically done (untested). * 404 handler done * Added textprocessors.php
Diffstat (limited to 'ratatoeskr')
-rw-r--r--ratatoeskr/backend/main.php61
-rw-r--r--ratatoeskr/cms_style/images/dead_emoticon.pngbin0 -> 230 bytes
-rw-r--r--ratatoeskr/cms_style/layout.css156
-rw-r--r--ratatoeskr/frontend.php748
-rw-r--r--ratatoeskr/main.php14
-rw-r--r--ratatoeskr/sys/default_settings.php3
-rw-r--r--ratatoeskr/sys/textprocessors.php78
-rw-r--r--ratatoeskr/templates/src/systemtemplates/content_write.html22
-rw-r--r--ratatoeskr/templates/src/systemtemplates/error.html17
-rwxr-xr-xratatoeskr/templates/src/systemtemplates/master.html61
-rw-r--r--ratatoeskr/translations/en.php34
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
new file mode 100644
index 0000000..1a07509
--- /dev/null
+++ b/ratatoeskr/cms_style/images/dead_emoticon.png
Binary files differ
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" => "&lt;-- previous page",
+ "page_next" => "next page --&gt;",
+ "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."
);
?>