diff --git a/.gitmodules b/.gitmodules index 73f90e0a..df07fdf3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "js/wPaint"] path = js/wPaint url = https://github.com/vichan-devel/wPaint.git + branch = master + +[submodule "inc/lib/parsedown"] + path = inc/lib/parsedown + url = https://github.com/vichan-devel/parsedown + branch = master diff --git a/inc/config.php b/inc/config.php index b6bc3b69..3fdb8d2f 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1498,6 +1498,9 @@ $config['mod']['ban_appeals'] = MOD; // View the recent posts page $config['mod']['recent'] = MOD; + // Create pages + $config['mod']['edit_pages'] = MOD; + $config['pages_max'] = 10; // Config editor permissions $config['mod']['config'] = array(); @@ -1702,3 +1705,6 @@ // Use CAPTCHA for reports? $config['report_captcha'] = false; + + // Allowed HTML tags in ?/edit_pages. + $config['allowed_html'] = 'a[href|title],p,br,li,ol,ul,strong,em,u,h2,b,i,tt,div,img[src|alt|title],hr'; diff --git a/inc/functions.php b/inc/functions.php index 378e40b3..7970d05a 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -20,6 +20,7 @@ require_once 'inc/events.php'; require_once 'inc/api.php'; require_once 'inc/mod/auth.php'; require_once 'inc/polyfill.php'; +//require_once 'inc/lib/parsedown/Parsedown.php'; // we don't need that right now, do we? if (!extension_loaded('gettext')) { require_once 'inc/lib/gettext/gettext.inc'; @@ -2739,3 +2740,45 @@ function link_for($post, $page50 = false, $foreignlink = false, $thread = false) return sprintf($tpl, $id, $slug); } + +function prettify_textarea($s){ + return str_replace("\t", ' ', str_replace("\n", ' ', htmlentities($s))); +} + +class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { + public $name = 'NoExternalImages'; + public function filter(&$uri, $c, $context) { + global $config; + $ct = $context->get('CurrentToken'); + + if (!$ct || $ct->name !== 'img') return true; + + if (!isset($uri->host) && !isset($uri->scheme)) return true; + + if (!in_array($uri->scheme . '://' . $uri->host . '/', $config['allowed_offsite_urls'])) { + error('No off-site links in board announcement images.'); + } + + return true; + } +} + +function purify_html($s) { + global $config; + + $c = HTMLPurifier_Config::createDefault(); + $c->set('HTML.Allowed', $config['allowed_html']); + $uri = $c->getDefinition('URI'); + $uri->addFilter(new HTMLPurifier_URIFilter_NoExternalImages(), $c); + $purifier = new HTMLPurifier($c); + $clean_html = $purifier->purify($s); + return $clean_html; +} + +function markdown($s) { + $pd = new Parsedown(); + $pd->setMarkupEscaped(true); + $pd->setimagesEnabled(false); + + return $pd->text($s); +} diff --git a/inc/mod/pages.php b/inc/mod/pages.php index ca12eaf1..328380d8 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -2628,6 +2628,167 @@ function mod_theme_rebuild($theme_name) { )); } +// This needs to be done for `secure` CSRF prevention compatibility, otherwise the $board will be read in as the token if editing global pages. +function delete_page_base($page = '', $board = false) { + global $config, $mod; + + if (empty($board)) + $board = false; + + if (!$board && $mod['boards'][0] !== '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_pages'], $board)) + error($config['error']['noaccess']); + + if ($board !== FALSE && !openBoard($board)) + error($config['error']['noboard']); + + if ($board) { + $query = prepare('DELETE FROM ``pages`` WHERE `board` = :board AND `name` = :name'); + $query->bindValue(':board', ($board ? $board : NULL)); + } else { + $query = prepare('DELETE FROM ``pages`` WHERE `board` IS NULL AND `name` = :name'); + } + $query->bindValue(':name', $page); + $query->execute() or error(db_error($query)); + + header('Location: ?/edit_pages' . ($board ? ('/' . $board) : ''), true, $config['redirect_http']); +} + +function mod_delete_page($page = '') { + delete_page_base($page); +} + +function mod_delete_page_board($page = '', $board = false) { + delete_page_base($page, $board); +} + +function mod_edit_page($id) { + global $config, $mod, $board; + + $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id'); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + $page = $query->fetch(); + + if (!$page) + error(_('Could not find the page you are trying to edit.')); + + if (!$page['board'] && $mod['boards'][0] !== '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_pages'], $page['board'])) + error($config['error']['noaccess']); + + if ($page['board'] && !openBoard($page['board'])) + error($config['error']['noboard']); + + if (isset($_POST['method'], $_POST['content'])) { + $content = $_POST['content']; + $method = $_POST['method']; + $page['type'] = $method; + + if (!in_array($method, array('markdown', 'html', 'infinity'))) + error(_('Unrecognized page markup method.')); + + switch ($method) { + case 'markdown': + $write = markdown($content); + break; + case 'html': + if (hasPermission($config['mod']['rawhtml'])) { + $write = $content; + } else { + $write = purify_html($content); + } + break; + case 'infinity': + $c = $content; + markup($content); + $write = $content; + $content = $c; + } + + if (!isset($write) or !$write) + error(_('Failed to mark up your input for some reason...')); + + $query = prepare('UPDATE ``pages`` SET `type` = :method, `content` = :content WHERE `id` = :id'); + $query->bindValue(':method', $method); + $query->bindValue(':content', $content); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + + $fn = ($board['uri'] ? ($board['uri'] . '/') : '') . $page['name'] . '.html'; + $body = "
+{% if board %} +{% set page_max = config.pages_max %} +{% trans %}This page allows you to create static pages for your board. The limit is {{ page_max }} pages per board. You will still have to link to your pages somewhere in your board, for example in a sticky or in the board's announcement. To make links in the board's announcement, use <a> HTML tags.{% endtrans %} +{% else %} +{% trans %}This page allows you to create static pages for your imageboard.{% endtrans %} +{% endif %} +