Commit 4aa6b7ff authored by Hugo Leisink's avatar Hugo Leisink

Release 6.4

parent 4dc83e62
banshee (6.4) stable; urgency=low
* Added form functionality to page module.
* Contact module removed.
* Small improvements.
-- Hugo Leisink <[email protected]> Fri, 22 Jun 2018 15:41:05 +0200
banshee (6.3) stable; urgency=low
* Re-post protection added.
......
......@@ -4,7 +4,8 @@ Banshee
Banshee is a PHP website framework, which aims at being secure, fast and easy
to use. It has a Model-View-Controller architecture (XSLT for the views).
Although it was designed to use MySQL as the database, other database
applications can be used as well with only little effort.
applications can be used as well with only little effort. Detailed information
about Banshee can be found at https://www.banshee-php.org/.
Ready to use modules like a forum, photo album, weblog, poll and a guestbook
will save web developers a lot of work when creating a new website. Easy to use
......
......@@ -2,7 +2,7 @@
"name": "hsleisink/banshee",
"description": "The Banshee PHP Content Management Framework",
"homepage": "https://www.banshee-php.org/",
"license": "Banshee",
"license": "MIT",
"type": "project",
"require": {
"php": ">=7.0.0"
......
......@@ -29,17 +29,45 @@
$this->view->set_layout($page["layout"]);
$this->view->allow_hiawatha_cache();
/* Page content
*/
$this->view->open_tag("page");
$this->view->add_tag("title", $page["title"]);
if (is_true(SECURE_XML_DATA)) {
$page["content"] = $this->view->secure_string($page["content"]);
}
$this->view->add_tag("content", $page["content"]);
if (is_true($page["form"])) {
$page_form = new Banshee\form_script($this->view, $this->settings, $page["content"]);
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if ($page_form->handle_post($_POST, $page["title"], $page["form_email"]) == false) {
$page["content"] = $page_form->generate_form($_POST);
} else {
$page["content"] = $page["form_done"];
if (substr($page["content"], 0, 1) != "<") {
$page["content"] = "<p>".$page["content"]."</p>\n";
}
$page["form"] = false;
}
} else {
$page["content"] = $page_form->generate_form();
}
}
if (is_true($page["form"])) {
$this->view->add_css("banshee/page_form.css");
$this->view->open_tag("form");
$this->view->add_tag("url", $page["url"]);
$this->view->add_tag("submit", $page["form_submit"]);
$this->view->add_tag("content", $page["content"]);
$this->view->close_tag();
} else {
$this->view->add_tag("content", $page["content"]);
$this->view->allow_hiawatha_cache();
}
if (is_true($page["back"])) {
$parts = explode("/", $this->page->page);
......
......@@ -21,6 +21,7 @@
"Dictionary" => array("cms/dictionary", "dictionary.png"),
"F.A.Q." => array("cms/faq", "faq.png"),
"Files" => array("cms/file", "file.png"),
"Forms" => array("cms/form", "form.png"),
"Forum" => array("cms/forum", "forum.png"),
"Guestbook" => array("cms/guestbook", "guestbook.png"),
"Languages" => array("cms/language", "language.png"),
......
......@@ -30,6 +30,7 @@
$page["private"] = show_boolean($page["private"]);
$page["visible"] = show_boolean($page["visible"]);
$page["back"] = show_boolean($page["back"]);
$page["form"] = show_boolean($page["form"]);
$args = array();
if (isset($page["id"])) {
......@@ -38,6 +39,7 @@
$this->view->add_javascript("cms/page.js");
$this->view->add_ckeditor("div.btn-group");
$this->view->add_help_button();
$args = array();
if ($page["preview"] != null) {
......
<?php
/* Copyright (c) by Hugo Leisink <[email protected]>
* This file is part of the Banshee PHP framework
* https://www.banshee-php.org/
*
* Licensed under The MIT License
*/
class contact_controller extends Banshee\controller {
private function show_contact_form($contact) {
$this->view->record($contact, "contact");
}
public function execute() {
$this->view->description = "Contact page";
$this->view->keywords = "contact";
$this->view->title = "Contact";
if ($_SERVER["REQUEST_METHOD"] == "POST") {
/* Send contact information
*/
if ($this->model->contact_oke($_POST) == false) {
$this->show_contact_form($_POST);
} else if ($this->model->send_contact($_POST) == false) {
$this->view->add_message("Error while sending contact information.");
$this->show_contact_form($_POST);
} else {
$this->view->add_tag("result", "Your contact information has been sent to the website owner.");
}
} else {
/* Show contact form
*/
$contact = array();
$this->show_contact_form($contact);
}
}
}
?>
......@@ -7,7 +7,7 @@
*/
class poll_controller extends Banshee\controller {
public function show_active_poll() {
private function show_active_poll() {
$poll = new Banshee\poll($this->db, $this->view, $this->settings);
if ($_SERVER["REQUEST_METHOD"] == "POST") {
......@@ -17,56 +17,60 @@
$poll->to_output();
}
public function execute() {
$this->view->description = "Poll";
$this->view->keywords = "poll";
private function show_poll($poll) {
$this->view->title = $poll["question"]." - Poll";
if (valid_input($this->page->parameters[0], VALIDATE_NUMBERS, VALIDATE_NONEMPTY)) {
/* Show poll
*/
if (($poll = $this->model->get_poll($this->page->parameters[0])) == false) {
$this->view->add_tag("result", "Poll not found");
} else {
$this->view->title = $poll["question"]." - Poll";
$this->view->open_tag("poll", array("id" => $poll["id"]));
$this->view->add_tag("question", $poll["question"]);
$this->view->open_tag("poll", array("id" => $poll["id"]));
$this->view->add_tag("question", $poll["question"]);
$votes = 0;
foreach ($poll["answers"] as $answer) {
$votes += (int)$answer["votes"];
}
$votes = 0;
foreach ($poll["answers"] as $answer) {
$votes += (int)$answer["votes"];
}
$this->view->open_tag("answers", array("votes" => $votes));
foreach ($poll["answers"] as $answer) {
unset($answer["poll_id"]);
$answer["percentage"] = ($votes > 0) ? round(100 * (int)$answer["votes"] / $votes) : 0;
$this->view->record($answer, "answer");
}
$this->view->close_tag();
$this->view->open_tag("answers", array("votes" => $votes));
foreach ($poll["answers"] as $answer) {
unset($answer["poll_id"]);
$answer["percentage"] = ($votes > 0) ? round(100 * (int)$answer["votes"] / $votes) : 0;
$this->view->record($answer, "answer");
}
$this->view->close_tag();
$this->view->close_tag();
}
$this->view->close_tag();
}
} else {
$this->show_active_poll();
private function show_poll_overview() {
$this->show_active_poll();
/* Poll overview
*/
$this->view->title = "Poll";
$this->view->title = "Poll";
if (($polls = $this->model->get_polls()) === false) {
$this->view->add_tag("result", "Database error");
} else {
$active_poll_id = $this->model->get_active_poll_id();
if (($polls = $this->model->get_polls()) === false) {
$this->view->add_tag("result", "Database error");
} else {
$active_poll_id = $this->model->get_active_poll_id();
$this->view->open_tag("polls");
foreach ($polls as $poll) {
if ($poll["id"] != $active_poll_id) {
$this->view->add_tag("question", $poll["question"], array("id" => $poll["id"]));
}
$this->view->open_tag("polls");
foreach ($polls as $poll) {
if ($poll["id"] != $active_poll_id) {
$this->view->add_tag("question", $poll["question"], array("id" => $poll["id"]));
}
$this->view->close_tag();
}
$this->view->close_tag();
}
}
public function execute() {
$this->view->description = "Poll";
$this->view->keywords = "poll";
if (valid_input($this->page->parameters[0], VALIDATE_NUMBERS, VALIDATE_NONEMPTY)) {
if (($poll = $this->model->get_poll($this->page->parameters[0])) == false) {
$this->view->add_tag("result", "Poll not found");
} else {
$this->show_poll($poll);
}
} else {
$this->show_poll_overview();
}
}
}
......
......@@ -82,7 +82,7 @@
foreach ($hits as $hit) {
$hit["text"] = strip_tags($hit["text"]);
$hit["content"] = strip_tags($hit["content"]);
$hit["content"] = preg_replace('/\[.*?\]/', "", $hit["content"]);
$hit["content"] = preg_replace('/\[.+?\]/', "", $hit["content"]); # Strip BBcodes
$hit["content"] = truncate_text($hit["content"], 400);
$this->view->record($hit, "hit");
}
......
......@@ -204,7 +204,7 @@
if ($this->page->parameters[2] == null) {
$this->view->title = "Year ".$this->page->parameters[1]." - Weblog";
} else {
$month = $months_of_year[$this->page->parameters[2] - 1];
$month = $months_of_year[(int)$this->page->parameters[2] - 1];
$this->view->title = $month." ".$this->page->parameters[1]." - Weblog";
}
......
This diff is collapsed.
<html>
<body>
<p>The following information has been submitted via the [TITLE] form at the <a href="[URL]">[WEBSITE] website</a>:</p>
[RESULT]
</body>
</html>
......@@ -9,9 +9,10 @@
namespace Banshee;
class captcha {
const FONT = "../extra/captcha_font.ttf";
private $code = null;
private $image = null;
private $font = "../extra/captcha_font.ttf";
/* Constructor
*
......@@ -48,17 +49,17 @@
imagefilledellipse($image, mt_rand(0, $width), mt_rand(0, $height), 1, 1, $dot_color);
}
if (($textbox = imagettfbbox($font_size, 0, $this->font, $this->code)) == false) {
if (($textbox = imagettfbbox($font_size, 0, self::FONT, $this->code)) == false) {
return;
}
$x = ($width - $textbox[4]) / 2;
$y = ($height - $textbox[5]) / 2;
if (imagettftext($image, $font_size, 0, $x + 1, $y + 1, $background_color, $this->font, $this->code) == false) {
if (imagettftext($image, $font_size, 0, $x + 1, $y + 1, $background_color, self::FONT, $this->code) == false) {
return;
}
if (imagettftext($image, $font_size, 0, $x, $y, $text_color, $this->font, $this->code) == false) {
if (imagettftext($image, $font_size, 0, $x, $y, $text_color, self::FONT, $this->code) == false) {
return;
}
......
......@@ -6,7 +6,7 @@
* Licensed under The MIT License
*/
define("BANSHEE_VERSION", "6.3");
define("BANSHEE_VERSION", "6.4");
define("ADMIN_ROLE_ID", 1);
define("USER_ROLE_ID", 2);
define("YES", 1);
......@@ -25,6 +25,7 @@
define("DAY", 86400);
define("LOG_DAYS", 60);
define("PAGE_MODULE", "banshee/page");
define("FORM_MODULE", "banshee/form");
define("ERROR_MODULE", "banshee/error");
define("LOGIN_MODULE", "banshee/login");
define("LOGOUT_MODULE", "logout");
......@@ -109,6 +110,24 @@
return (is_true($bool) ? "yes" : "no");
}
/* Convert empty string to null
*
* INPUT: string
* OUTPUT: string|null
* ERROR: -
*/
function null_if_empty($str) {
if (is_string($str) == false) {
return $str;
}
if (trim($str) == "") {
$str = null;
}
return $str;
}
/* Convert a page path to a module path
*
* INPUT: array / string page path
......
......@@ -9,8 +9,9 @@
namespace Banshee\Core;
class settings {
const MAX_VALUE_LEN = 25;
private $db = null;
private $max_value_len = 256;
private $cache = array();
private $types = array("boolean", "float", "integer", "string");
......@@ -124,7 +125,7 @@
* ERROR: false
*/
private function store($key, $type, $value) {
if (strlen($value) > $this->max_value_len) {
if (strlen($value) > self::MAX_VALUE_LEN) {
return false;
}
......
......@@ -176,6 +176,8 @@
if ($webserver != "Hiawatha") {
$result = false;
} else if ($this->user->logged_in) {
$result = false;
} else if ($this->settings->hiawatha_cache_enabled == false) {
$result = false;
} else if ($this->hiawatha_cache_time === null) {
......@@ -607,7 +609,9 @@
header("X-Frame-Options: sameorigin");
header("X-XSS-Protection: 1; mode=block");
header("X-Content-Type-Options: nosniff");
header("Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src * data:");
if (CSP != "") {
header("Content-Security-Policy: ".CSP);
}
header("Referrer-Policy: same-origin");
#header("Expect-CT: max-age=0; report-uri=\"".$_SERVER["HTTP_SCHEME"]."://".$_SERVER["SERVER_NAME"]."/report_ct_error\"");
......
<?php
/* Copyright (c) by Hugo Leisink <[email protected]>
* This file is part of the Banshee PHP framework
* https://www.banshee-php.org/
*
* Licensed under The MIT License
*/
namespace Banshee;
class form_script {
const OPEN = "{{";
const CLOSE = "}}";
private $view = null;
private $settings = null;
private $content = null;
private $elements = array();
/* Constructor
*
* INPUT: object view, object settings, string content containing script
* OUTPUT: -
* ERROR: -
*/
public function __construct($view, $settings, $content) {
$this->view = $view;
$this->settings = $settings;
$this->content = $content;
$open_len = strlen(self::OPEN);
$close_len = strlen(self::CLOSE);
$begin = 0;
while (($begin = strpos($content, self::OPEN, $begin)) !== false) {
if (($end = strpos($content, self::CLOSE, $begin)) !== false) {
$element = substr($content, $begin + $open_len, $end - $begin - $close_len);
list($type, $label) = explode(" ", $element, 2);
if ($required = ($type == "required")) {
list($type, $label) = explode(" ", $label, 2);
}
$element = array(
"type" => $type,
"label" => $label,
"pos" => $begin,
"length" => $end + $close_len - $begin,
"required" => $required);
array_push($this->elements, $element);
}
$begin += ($end - $begin) + $close_len;
}
}
/* Valid form script
*
* INPUT: -
* OUTPUT: boolean valid form
* ERROR: -
*/
public function valid_script() {
$result = true;
$valid_types = array("line", "email", "number", "text", "checkbox", "choice", "date");
foreach ($this->elements as $elem_id => $element) {
if (in_array($element["type"], $valid_types) == false) {
$this->view->add_message("Invalid form element type '%s'.", $element["type"]);
$result = false;
} else if (trim($element["label"] == "")) {
$this->view->add_message("Empty label for form element with type '%s'.", $element["type"]);
$result = false;
} else if ($element["type"] == "choice") {
list($element["label"], $answers) = explode(":", $element["label"], 2);
$answers = explode("/", $answers);
if (count($answers) < 2) {
$this->view->add_message("Too few options for 'choice' with label '%s'.", $element["label"]);
$result = false;
}
}
}
return $result;
}
/* Generate form
*
* INPUT: [array POST data]
* OUTPUT: string HTML
* ERROR: -
*/
public function generate_form($post = array()) {
$html = $this->content;
$elements = array_reverse($this->elements, true);
foreach ($elements as $elem_id => $element) {
switch ($element["type"]) {
case "line":
case "email":
case "number":
$item = "<input type=\"text\" id=\"".$elem_id."\" name=\"".$elem_id."\" value=\"".$post[$elem_id]."\" class=\"form-control\" >\n";
break;
case "text";
$item = "<textarea id=\"".$elem_id."\" name=\"".$elem_id."\" class=\"form-control\">".$post[$elem_id]."</textarea>\n";
break;
case "checkbox":
$item = "<input type=\"checkbox\" id=\"".$elem_id."\" name=\"".$elem_id."\"".(is_true($post[$elem_id]) ? " checked" : "")." />\n";
break;
case "choice":
list($element["label"], $answers) = explode(":", $element["label"], 2);
$answers = explode("/", $answers);
$item = "<select id=\"".$elem_id."\" name=\"".$elem_id."\" class=\"form-control\">\n";
if ($element["required"] == false) {
$item .= "<option".($post[$elem_id] == "" ? " selected" : "")."></option>\n";
}
foreach ($answers as $answer) {
$item .= "<option".($post[$elem_id] == $answer ? " selected" : "").">".$answer."</option>\n";
}
$item .= "</select>\n";
break;
case "date":
$this->view->add_javascript("jquery/jquery-ui.js");
$this->view->add_javascript("banshee/datepicker.js");
$this->view->add_css("jquery/jquery-ui.css");
$item = "<input type=\"text\" id=\"".$elem_id."\" name=\"".$elem_id."\" value=\"".$post[$elem_id]."\" class=\"form-control datepicker\" >\n";
break;
default:
$item = "<div>Unknown form element defined.</div>\n";
}
$item = "<label for=\"".$elem_id."\">".$element["label"].":".
($element["required"] ? "<span class=\"required\">*</span>" : "").
"</label>\n".$item;
$head = substr($html, 0, $element["pos"]);
$tail = substr($html, $element["pos"] + $element["length"]);
$html = $head.$item.$tail;
}
return $html;
}
/* Handle POST
*
* INPUT: array POST data, string page title, string recipient e-mail address
* OUTPUT: true
* ERROR: false
*/
public function handle_post($post, $page_title, $email_address) {
$sender = $this->settings->webmaster_email;
$valid_post = true;
foreach ($this->elements as $elem_id => $element) {
if ($post[$elem_id] != "") {
switch ($element["type"]) {
case "checkbox":
$post[$elem_id] = show_boolean($post[$elem_id]);
break;
case "email":
if (valid_email($post[$elem_id]) == false) {
$this->view->add_message("The field '%s' does not contain a valid e-mail address.", $element["label"]);
$valid_post = false;
}
$sender = $post[$elem_id];
break;
case "number":
if (valid_input($post[$elem_id], VALIDATE_NUMBERS) == false) {
$this->view->add_message("The field '%s' does not contain a valid number.", $element["label"]);
$valid_post = false;
}
break;
case "date":
if (valid_date($post[$elem_id]) == false) {
$this->view->add_message("The field '%s' does not contain a date.", $element["label"]);
$valid_post = false;
}
}
} else if ($element["required"]) {
$this->view->add_message("The field '%s' cannot be empty.", $element["label"]);
$valid_post = false;
}
}
if ($valid_post == false) {
return false;
}
$result = "<table>\n";
foreach ($this->elements as $elem_id => $element) {
$result .= sprintf("<tr><td>%s:</td><td>%s</td></tr>\n", $element["label"], $post[$elem_id]);
}
$result .= "</table>\n";
$subject = sprintf("Submit at %s form at %s website", $page_title, $this->settings->head_title);
$mail = new Protocols\email($subject, $sender);
$message = file_get_contents("../extra/form_submit.txt");
$mail->set_message_fields(array(
"RESULT" => $result,
"TITLE" => $page_title,
"URL" => $_SERVER["HTTP_SCHEME"]."://".$_SERVER["SERVER_NAME"],
"WEBSITE" => $this->settings->head_title));
$mail->message($message);
if ($mail->send($email_address) == false) {
$this->view->add_message("Error while sending form data via e-mail.");
return false;
}
return true;
}
}
?>
......@@ -65,8 +65,8 @@
switch ($key) {
case "map_type": if (in_array($value, $this->map_types)) $this->map_type = $value; break;
case "format": if (in_array($value, $this->formats)) $this->format = $value; break;
case "route_weight": $this->path_weight = $value; break;
case "route_color": $this->path_color = $value; break;
case "path_weight": $this->path_weight = $value; break;
case "path_color": $this->path_color = $value; break;
case "center": $this->center = $value; break;
case "zoom": $this->zoom = $value; break;
}
......
......@@ -113,7 +113,7 @@
}
$answer_id = $answers[$answer]["id"];
setcookie("last_poll_id", (int)$poll["id"], time() + 100 * DAY);
setcookie("last_poll_id", (int)$poll["id"], time() + 100 * DAY, "/");
$_COOKIE["last_poll_id"] = (int)$poll["id"];
/* Log selected item
......
......@@ -145,7 +145,8 @@
} else {
/* You're probably just dealing with a spam bot
*/
$this->user->log_action("POST without token.");
$logfile = new logfile("spam");
$logfile->add_entry("POST without token.");
}