diff --git a/.gitmodules b/.gitmodules
index 098b04e2..69ee4d6c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -2,3 +2,8 @@
path = js/wPaint
url = https://github.com/ctrlcctrlv/wPaint.git
branch = master
+
+[submodule "inc/lib/parsedown"]
+ path = inc/lib/parsedown
+ url = https://github.com/vichan-devel/parsedown
+ branch = master
diff --git a/LICENSE.md b/LICENSE.md
index 942ac82a..677d15b1 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,3 +1,26 @@
+# License of infinity
+Copyright (c) 2013-2015 Infinity Development Group (https://github.com/ctrlcctrlv/infinity)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+All copyright notices and permission notices (including this file) shall be
+included and remain unedited in all copies or substantial portions of the
+Software. This explicitly includes but is not limited to the vichan copyright
+notices found in the footers of some template files.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
# License of vichan
Copyright (c) 2012-2014 vichan-devel
diff --git a/README.md b/README.md
index ecc9e239..46abe61d 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ infinity
About
------------
-infinity is a fork of vichan, with the difference that infinity is geared towards allowing users to create their own boards. A running instance is at [8ch.net](https://8ch.net/)
+infinity is a fork of vichan, with the difference that infinity is geared towards allowing users to create their own boards. A running instance is at [8ch.net](https://8ch.net/) (new! a user of the software wrote to me that they created a Polish version: [8ch.pl](http://8ch.pl/))
Most things (other than installation) that apply to upstream vichan also apply to infinity. See their readme for a detailed FAQ: https://github.com/vichan-devel/vichan/blob/master/README.md
@@ -15,12 +15,12 @@ Basic requirements:
A computer running a Unix or Unix-like OS(infinity has been specifically tested with and is known to work under Ubuntu 14.x), Apache, MySQL, and PHP
* Make sure Apache has read/write access to the directory infinity resides in.
* `install.php` is not maintained. Don't use it.
-* As of February 22, 2015, you need the [DirectIO module (dio.so)](http://php.net/manual/en/ref.dio.php).
+* As of February 22, 2015, you need the [DirectIO module (dio.so)](http://php.net/manual/en/ref.dio.php). This is for compatibility with NFS.
Step 1. Create infinity's database from the included install.sql file. Enter mysql and create an empty database named 'infinity'. Then cd into the infinity base directory and run:
```
mysql -uroot -p infinity < install.sql
-echo 'infinity' > .installed
+echo '+ infinity '`git rev-parse HEAD|head -c 10` > .installed
```
Step 2. /inc/secrets.php does not exist by default, but infinity needs it in order to function. To fix this, cd into /inc/ and run:
@@ -40,18 +40,12 @@ Now open secrets.php and edit the $config['db'] settings to point to the 'infini
$config['cache']['enabled'] = 'apc';
```
-Step 3.(Optional) By default, infinity will ignore any changes you make to the template files until you log into mod.php, go to Rebuild, and select Flush Cache. You may find this inconvenient. To make infinity automatically accept your changes to the template files, open /inc/template.php and add:
+Step 3.(Optional) By default, infinity will ignore any changes you make to the template files until you log into mod.php, go to Rebuild, and select Flush Cache. You may find this inconvenient. To make infinity automatically accept your changes to the template files, set $config['twig_cache'].
+
+Step 4. Infinity can function in a *very* barebones fashion after the first two steps, but you should probably install these additional packages if you want to seriously run it and/or contribute to it. ffmpeg may fail to install under certain versions of Ubuntu. If it does, remove it from this script and install it via an alternate method. Make sure to run the below as root:
```
-'auto_reload' => true
-```
-
-To the array of settings passed to Twig_Environment().
-
-Step 4. Infinity can function in a very barebones fashion after the first two steps, but you should probably install these additional packages if you want to seriously run it and/or contribute to it. ffmpeg may fail to install under certain versions of Ubuntu. If it does, remove it from this script and install it via an alternate method. Make sure to run the below as root:
-
-```
-apt-get install graphicsmagick gifsicle php5-fpm mysql-client php5-mysql php5-cli php-pear php5-apcu; add-apt-repository ppa:jon-severinsson/ffmpeg; add-apt-repository ppa:nginx/stable; apt-get update; apt-get install nginx ffmpeg; pear install Net_DNS2
+apt-get install graphicsmagick gifsicle php5-fpm mysql-client php5-mysql php5-cli php-pear php5-apcu php5-dev; add-apt-repository ppa:jon-severinsson/ffmpeg; add-apt-repository ppa:nginx/stable; apt-get update; apt-get install nginx ffmpeg; pear install Net_DNS2; pecl install "channel://pecl.php.net/dio-0.0.7"
```
Step 5. The current captcha provider listed inc/config.php is dead. You may want to work around this.
diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php
index b8a28fe8..f437f92e 100644
--- a/inc/8chan-mod-pages.php
+++ b/inc/8chan-mod-pages.php
@@ -1,42 +1,4 @@
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;
- }
- }
- }
-
- if (!function_exists('purify')){
- function purify($s){
- $c = HTMLPurifier_Config::createDefault();
- $c->set('HTML.Allowed', 'a[href|title],p,br,li,ol,ul,strong,em,u,h2,b,i,tt,div,img[src|alt|title],hr');
- $uri = $c->getDefinition('URI');
- $uri->addFilter(new HTMLPurifier_URIFilter_NoExternalImages(), $c);
- $purifier = new HTMLPurifier($c);
- $clean_html = $purifier->purify($s);
- return $clean_html;
- }
- }
-
if (!function_exists('is_billion_laughs')){
function is_billion_laughs($arr1, $arr2) {
$arr = array();
@@ -482,7 +444,7 @@ FLAGS;
}
$anonymous = base64_encode($_POST['anonymous']);
- $blotter = base64_encode(purify(html_entity_decode($_POST['blotter'])));
+ $blotter = base64_encode(purify_html(html_entity_decode($_POST['blotter'])));
$add_to_config = @file_get_contents($b.'/extra_config.php');
$replace = '';
@@ -633,8 +595,6 @@ EOT;
file_write('8archive.json', json_encode($query->fetchAll(PDO::FETCH_ASSOC)));
file_write($b.'/config.php', $config_file);
file_write('stylesheets/board/'.$b.'.css', $clean_css);
- file_write($b.'/rules.html', Element('page.html', array('title'=>'Rules', 'subtitle'=>'', 'config'=>$config, 'body'=>'
'.purify($_POST['rules']).'
')));
- file_write($b.'/rules.txt', $_POST['rules']);
$_config = $config;
unset($config['wordfilters']);
@@ -665,7 +625,6 @@ EOT;
$query->execute() or error(db_error($query));
$board = $query->fetchAll()[0];
- $rules = @file_get_contents($board['uri'] . '/rules.txt');
$css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css');
if ($config['cache']['enabled']) {
@@ -673,5 +632,5 @@ EOT;
cache::delete('all_boards');
}
- mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'rules'=>prettify_textarea($rules), 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages,'allowed_urls'=>$config['allowed_offsite_urls']));
+ mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages,'allowed_urls'=>$config['allowed_offsite_urls']));
};
diff --git a/inc/config.php b/inc/config.php
index 64b2836b..37925a3b 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -1541,6 +1541,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();
@@ -1731,3 +1734,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 4c9bb3d7..8e168507 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/bans.php';
require_once 'inc/lib/gettext/gettext.inc';
+require_once 'inc/lib/parsedown/Parsedown.php'; // todo: option for parsedown instead of Tinyboard/STI markup
require_once 'inc/mod/auth.php';
// the user is not currently logged in as a moderator
@@ -2489,3 +2490,45 @@ function less_hostmask($hostmask) {
return implode('.', $parts);
}
+
+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/lib/parsedown b/inc/lib/parsedown
new file mode 160000
index 00000000..d264130a
--- /dev/null
+++ b/inc/lib/parsedown
@@ -0,0 +1 @@
+Subproject commit d264130ab956c54d6bdf76a1b9ae18acb5ddc44f
diff --git a/inc/mod/pages.php b/inc/mod/pages.php
index 1e7cb32d..5880a4fb 100644
--- a/inc/mod/pages.php
+++ b/inc/mod/pages.php
@@ -3337,6 +3337,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 = "$write
";
+ $html = Element('page.html', array('config' => $config, 'body' => $body, 'title' => utf8tohtml($page['title'])));
+ file_write($fn, $html);
+ }
+
+ if (!isset($content)) {
+ $query = prepare('SELECT `content` FROM ``pages`` WHERE `id` = :id');
+ $query->bindValue(':id', $id);
+ $query->execute() or error(db_error($query));
+ $content = $query->fetchColumn();
+ }
+
+ mod_page(sprintf(_('Editing static page: %s'), $page['name']), 'mod/edit_page.html', array('page' => $page, 'token' => make_secure_link_token("edit_page/$id"), 'content' => prettify_textarea($content), 'board' => $board));
+}
+
+function mod_pages($board = false) {
+ global $config, $mod, $pdo;
+
+ 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('SELECT * FROM ``pages`` WHERE `board` = :board');
+ $query->bindValue(':board', $board);
+ } else {
+ $query = query('SELECT * FROM ``pages`` WHERE `board` IS NULL');
+ }
+ $query->execute() or error(db_error($query));
+ $pages = $query->fetchAll(PDO::FETCH_ASSOC);
+
+ if (isset($_POST['page'])) {
+ if ($board and sizeof($pages) > $config['pages_max'])
+ error(sprintf(_('Sorry, this site only allows %d pages per board.'), $config['pages_max']));
+
+ if (!preg_match('/^[a-z0-9]{1,255}$/', $_POST['page']))
+ error(_('Page names must be < 255 chars and may only contain lowercase letters A-Z and digits 1-9.'));
+
+ foreach ($pages as $i => $p) {
+ if ($_POST['page'] === $p['name'])
+ error(_('Refusing to create a new page with the same name as an existing one.'));
+ }
+
+ $title = ($_POST['title'] ? $_POST['title'] : NULL);
+
+ $query = prepare('INSERT INTO ``pages``(board, title, name) VALUES(:board, :title, :name)');
+ $query->bindValue(':board', ($board ? $board : NULL));
+ $query->bindValue(':title', $title);
+ $query->bindValue(':name', $_POST['page']);
+ $query->execute() or error(db_error($query));
+
+ $pages[] = array('id' => $pdo->lastInsertId(), 'name' => $_POST['page'], 'board' => $board, 'title' => $title);
+ }
+
+ foreach ($pages as $i => &$p) {
+ $p['delete_token'] = make_secure_link_token('edit_pages/delete/' . $p['name'] . ($board ? ('/' . $board) : ''));
+ }
+
+ mod_page(_('Pages'), 'mod/pages.html', array('pages' => $pages, 'token' => make_secure_link_token('edit_pages' . ($board ? ('/' . $board) : '')), 'board' => $board));
+}
+
function mod_debug_antispam() {
global $pdo, $config;
@@ -3459,3 +3620,4 @@ function mod_debug_apc() {
mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars));
}
+
diff --git a/install.sql b/install.sql
index 0e279283..7990c0a8 100644
--- a/install.sql
+++ b/install.sql
@@ -372,6 +372,23 @@ CREATE TABLE `filters` (
UNIQUE KEY `data` (`type`,`value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `pages`
+--
+
+CREATE TABLE `pages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `board` varchar(255) DEFAULT NULL,
+ `name` varchar(255) NOT NULL,
+ `title` varchar(255) DEFAULT NULL,
+ `type` varchar(255) DEFAULT NULL,
+ `content` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `u_pages` (`name`,`board`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/mod.php b/mod.php
index bc9d6057..4a5d18c6 100644
--- a/mod.php
+++ b/mod.php
@@ -35,6 +35,11 @@ $pages = array(
'/edit_news' => 'secure_POST news', // view news
'/edit_news/(\d+)' => 'secure_POST news', // view news
'/edit_news/delete/(\d+)' => 'secure news_delete', // delete from news
+
+ '/edit_pages(?:/?(\%b)?)' => 'secure_POST pages',
+ '/edit_page/(\d+)' => 'secure_POST edit_page',
+ '/edit_pages/delete/([a-z0-9]+)' => 'secure delete_page',
+ '/edit_pages/delete/([a-z0-9]+)/(\%b)' => 'secure delete_page_board',
'/noticeboard' => 'secure_POST noticeboard', // view noticeboard
'/noticeboard/(\d+)' => 'secure_POST noticeboard', // view noticeboard
diff --git a/templates/8chan/index.html b/templates/8chan/index.html
index c93c0b1c..19b42dc1 100644
--- a/templates/8chan/index.html
+++ b/templates/8chan/index.html
@@ -326,10 +326,6 @@
diff --git a/templates/generic_page.html b/templates/generic_page.html
index 4cd31162..2d53bea6 100644
--- a/templates/generic_page.html
+++ b/templates/generic_page.html
@@ -40,6 +40,7 @@
vichan {{ config.version }} -
Tinyboard Copyright © 2010-2014 Tinyboard Development Group
vichan Copyright © 2012-2014 vichan-devel
+
infinity Copyright © 2013-2015 Fredrick Brennan & Infinity Development Group
{% for footer in config.footer %}{{ footer }}
{% endfor %}
+
+
+{% 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 %}
+
{% trans %}Existing pages{% endtrans %}
+{% if pages %}
+
+
+
{% trans %}Create a new page{% endtrans %}
+
+
+
diff --git a/templates/mod/settings.html b/templates/mod/settings.html
index c0f43ced..97aebec9 100644
--- a/templates/mod/settings.html
+++ b/templates/mod/settings.html
@@ -76,11 +76,11 @@
{% trans %}Edit board flags{% endtrans %}
{% trans %}Edit board volunteers{% endtrans %}
{% trans %}Edit board tags{% endtrans %}
+ {% trans %}Edit board pages{% endtrans %}
diff --git a/templates/page.html b/templates/page.html
index a3916b6f..06bdc439 100644
--- a/templates/page.html
+++ b/templates/page.html
@@ -27,6 +27,7 @@
vichan {{ config.version }} -
Tinyboard Copyright © 2010-2014 Tinyboard Development Group
vichan Copyright © 2012-2014 vichan-devel
+
infinity Copyright © 2013-2015 Fredrick Brennan & Infinity Development Group
{% for footer in config.footer %}{{ footer }}
{% endfor %}