-
{% trans %}You are banned! ;_;{% endtrans %}
+ {% if ban.expires and time() >= ban.expires %}
+
{% trans %}You were banned! ;_;{% endtrans %}
+ {% else %}
+
{% trans %}You are banned! ;_;{% endtrans %}
+ {% endif %}
- {% trans %}You have been banned from{% endtrans %}
+ {% if ban.expires and time() >= ban.expires %}
+ {% trans %}You were banned from{% endtrans %}
+ {% else %}
+ {% trans %}You have been banned from{% endtrans %}
+ {% endif %}
{% if ban.board %}
{{ config.board_abbreviation|sprintf(ban.board) }}
{% else %}
@@ -23,7 +31,9 @@
{% trans %}Your ban was filed on{% endtrans %}
{{ ban.set|date(config.ban_date) }} {% trans %}and{% endtrans %}
- {% if ban.expires %}
+ {% if ban.expires and time() >= ban.expires %}
+ {% trans %} has since expired. Refresh the page to continue.{% endtrans %}
+ {% elseif ban.expires %}
{% trans %}expires{% endtrans %} {{ ban.expires|until }} {% trans %}from now, which is on{% endtrans %}
{{ ban.expires|date(config.ban_date) }}
diff --git a/templates/boardlist.html b/templates/boardlist.html
new file mode 100644
index 00000000..62646845
--- /dev/null
+++ b/templates/boardlist.html
@@ -0,0 +1 @@
+I'm your overboard boardlist. You can put here anything and I reside in templates/boardlist.html
diff --git a/templates/index.html b/templates/index.html
index eaed3666..451ca8b1 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -1,9 +1,21 @@
- {% include 'header.html' %}
- {{ board.url }} - {{ board.name }}
+
+
+
+ {% include 'header.html' %}
+ {{ board.url }} - {{ board.title|e }}
{{ boardlist.top }}
@@ -20,7 +32,11 @@
{% include 'attention_bar.html' %}
- {% include 'post_form.html' %}
+ {% if not no_post_form %}
+ {% include 'post_form.html' %}
+ {% else %}
+ {% include 'boardlist.html' %}
+ {% endif %}
{% if config.blotter %}{{ config.blotter }}
{% endif %}
diff --git a/templates/main.js b/templates/main.js
index b0a96393..31c0254a 100644
--- a/templates/main.js
+++ b/templates/main.js
@@ -1,5 +1,27 @@
{% raw %}
+/* gettext-compatible _ function, example of usage:
+ *
+ * > // Loading pl_PL.json here (containing polish translation strings generated by tools/i18n_compile.php)
+ * > alert(_("Hello!"));
+ * Witaj!
+ */
+function _(s) {
+ return (typeof l10n != 'undefined' && typeof l10n[s] != 'undefined') ? l10n[s] : s;
+}
+
+/* printf-like formatting function, example of usage:
+ *
+ * > alert(fmt("There are {0} birds on {1} trees", [3,4]));
+ * There are 3 birds on 4 trees
+ * > // Loading pl_PL.json here (containing polish translation strings generated by tools/locale_compile.php)
+ * > alert(fmt(_("{0} users"), [3]));
+ * 3 uzytkownikow
+ */
+function fmt(s,a) {
+ return s.replace(/\{([0-9]+)\}/g, function(x) { return a[x[1]]; });
+}
+
var saved = {};
@@ -105,7 +127,7 @@ function generatePassword() {
function dopost(form) {
if (form.elements['name']) {
- localStorage.name = form.elements['name'].value.replace(/ ##.+$/, '');
+ localStorage.name = form.elements['name'].value.replace(/( |^)## .+$/, '');
}
if (form.elements['email'] && form.elements['email'].value != 'sage') {
localStorage.email = form.elements['email'].value;
diff --git a/templates/mod/ban_list.html b/templates/mod/ban_list.html
index 2edb9cdf..f8826ade 100644
--- a/templates/mod/ban_list.html
+++ b/templates/mod/ban_list.html
@@ -10,6 +10,7 @@
{% trans 'Set' %}
{% trans 'Duration' %}
{% trans 'Expires' %}
+ {% trans 'Seen' %}
{% trans 'Staff' %}
{% for ban in bans %}
@@ -58,6 +59,13 @@
{% endif %}
{% endif %}
+
+ {% if ban.seen %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+
{% if ban.username %}
{% if mod|hasPermission(config.mod.view_banstaff) %}
diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html
index 2f0b03ad..e54b3cd3 100644
--- a/templates/mod/dashboard.html
+++ b/templates/mod/dashboard.html
@@ -101,6 +101,7 @@
+{#
{% trans 'Search' %}
@@ -115,6 +116,7 @@
+#}
{% if config.debug %}
diff --git a/templates/mod/view_ip.html b/templates/mod/view_ip.html
index c49880ae..e2dad7af 100644
--- a/templates/mod/view_ip.html
+++ b/templates/mod/view_ip.html
@@ -136,6 +136,16 @@
{% endif %}
+
+ {% trans 'Seen' %}
+
+ {% if ban.seen %}
+ {% trans 'Yes' %}
+ {% else %}
+ {% trans 'No' %}
+ {% endif %}
+
+
{% trans 'Staff' %}
@@ -161,3 +171,43 @@
{% include 'mod/ban_form.html' %}
{% endif %}
+
+{% if logs|count > 0 %}
+
+ History
+
+
+ {% trans 'Staff' %}
+ {% trans 'Time' %}
+ {% trans 'Board' %}
+ {% trans 'Action' %}
+
+ {% for log in logs %}
+
+
+ {% if log.username %}
+ {{ log.username|e }}
+ {% elseif log.mod == -1 %}
+ system
+ {% else %}
+ {% trans 'deleted?' %}
+ {% endif %}
+
+
+ {{ log.time|ago }}
+
+
+ {% if log.board %}
+ {{ config.board_abbreviation|sprintf(log.board) }}
+ {% else %}
+ -
+ {% endif %}
+
+
+ {{ log.text }}
+
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/templates/page.html b/templates/page.html
index 6e7a8a18..07eea62e 100644
--- a/templates/page.html
+++ b/templates/page.html
@@ -1,8 +1,8 @@
- {% include 'header.html' %}
+ {% include 'header.html' %}
{{ title }}
diff --git a/templates/post_form.html b/templates/post_form.html
index 01b642d3..64451fb9 100755
--- a/templates/post_form.html
+++ b/templates/post_form.html
@@ -24,24 +24,22 @@
{{ antibot.html() }}
+ {% if not (not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri))) %}
+ {% if config.spoiler_images %} {% trans %}Spoiler Image{% endtrans %} {% endif %}
+ {% endif %}
{% endif %}
-
- {% if not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %}
+ {% if not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %}
+
{% trans %}Subject{% endtrans %}
{{ antibot.html() }}
- {% else %}
- {% trans %}Submit{% endtrans %}
- {{ antibot.html() }}
-
-
- {% endif %}
{% if config.spoiler_images %} {% trans %}Spoiler Image{% endtrans %} {% endif %}
+ {% endif %}
{% trans %}Comment{% endtrans %}
@@ -50,6 +48,11 @@
{{ antibot.html() }}
+ {% if not (not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri))) %}
+ {% if not (not config.field_disable_email or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri))) %}
+ {% if config.spoiler_images %} {% trans %}Spoiler Image{% endtrans %} {% endif %}
+ {% endif %}
+ {% endif %}
{% if config.recaptcha %}
@@ -59,7 +62,7 @@
{{ antibot.html() }}
-
+
{{ antibot.html() }}
diff --git a/templates/post_reply.html b/templates/post_reply.html
index 73ff2c15..450f8246 100644
--- a/templates/post_reply.html
+++ b/templates/post_reply.html
@@ -7,14 +7,14 @@
{% if post.subject|length > 0 %}
{# show subject #}
- {{ post.subject }}
+ {{ post.subject|bidi_cleanup }}
{% endif %}
{% if post.email|length > 0 %}
{# start email #}
{% endif %}
{% set capcode = post.capcode|capcode %}
- {{ post.name }}
+ {{ post.name|bidi_cleanup }}
{% if post.trip|length > 0 %}
{{ post.trip }}
{% endif %}
@@ -66,9 +66,9 @@
{% if config.show_filename and post.filename %}
,
{% if post.filename|length > config.max_filename_display %}
- {{ post.filename|truncate(config.max_filename_display) }}
+ {{ post.filename|truncate(config.max_filename_display)|bidi_cleanup }}
{% else %}
- {{ post.filename }}
+ {{ post.filename|bidi_cleanup }}
{% endif %}
{% endif %}
{% if post.thumb != 'file' and config.image_identification %}
diff --git a/templates/post_thread.html b/templates/post_thread.html
index 6bc0694f..edc07011 100644
--- a/templates/post_thread.html
+++ b/templates/post_thread.html
@@ -1,152 +1,148 @@
-{% filter remove_whitespace %}
-{# tabs and new lines will be ignored #}
-
-
-
-{% if post.embed %}
- {{ post.embed }}
-{% elseif post.file == 'deleted' %}
-
-{% elseif post.file and post.file %}
-
{% trans %}File:{% endtrans %} {{ post.file }}
- (
- {% if post.thumb == 'spoiler' %}
- {% trans %}Spoiler Image{% endtrans %},
- {% endif %}
- {{ post.filesize|filesize }}
- {% if post.filex and post.filey %}
- , {{ post.filex}}x{{ post.filey }}
- {% if config.show_ratio %}
- , {{ post.ratio }}
- {% endif %}
- {% endif %}
- {% if config.show_filename and post.filename %}
- ,
- {% if post.filename|length > config.max_filename_display %}
- {{ post.filename|truncate(config.max_filename_display) }}
- {% else %}
- {{ post.filename }}
- {% endif %}
- {% endif %}
- {% if post.thumb != 'file' and config.image_identification %}
- ,
-
- io
- {% if post.file|extension == 'jpg' %}
- e
- {% endif %}
- g
- t
-
- {% endif %}
- )
-
-
-
-{% endif %}
-
-
-
- {% if post.subject|length > 0 %}
- {# show subject #}
- {{ post.subject }}
- {% endif %}
- {% if post.email|length > 0 %}
- {# start email #}
-
- {% endif %}
- {% set capcode = post.capcode|capcode %}
- {{ post.name }}
- {% if post.trip|length > 0 %}
- {{ post.trip }}
- {% endif %}
- {% if post.email|length > 0 %}
- {# end email #}
-
- {% endif %}
- {% if capcode %}
- {{ capcode.cap }}
- {% endif %}
- {% if post.mod and post.mod|hasPermission(config.mod.show_ip, board.uri) %}
- [{{ post.ip }} ]
- {% endif %}
-
- {{ post.time|date(config.post_date) }}
-
- {% if config.poster_ids %}
- ID: {{ post.ip|poster_id(post.id) }}
- {% endif %}
-
- #
-
- No.
-
- {{ post.id }}
-
- {% if post.sticky %}
-
- {% endif %}
- {% if post.locked %}
-
- {% endif %}
- {% if post.bumplocked and (config.mod.view_bumplock < 0 or (post.mod and post.mod|hasPermission(config.mod.view_bumplock, board.uri))) %}
-
- {% endif %}
- {% if index %}
- [{% trans %}Reply{% endtrans %}]
- {% endif %}
- {{ post.postControls }}
-
-
- {% endfilter %}{% if index %}{{ post.body|truncate_body(post.link) }}{% else %}{{ post.body }}{% endif %}{% filter remove_whitespace %}
-
- {% if post.omitted or post.omitted_images %}
-
- {% if post.omitted %}
- {% trans %}
- 1 post
- {% plural post.omitted %}
- {{ count }} posts
- {% endtrans %}
- {% if post.omitted_images %}
- {% trans %}and{% endtrans %}
- {% endif %}
- {% endif %}
- {% if post.omitted_images %}
- {% trans %}
- 1 image reply
- {% plural post.omitted_images %}
- {{ count }} image replies
- {% endtrans %}
- {% endif %} {% trans %}omitted. Click reply to view.{% endtrans %}
-
- {% endif %}
-{% if not index %}
-{% endif %}
-
{% endfilter %}
-{% set hr = post.hr %}
-{% for post in post.posts %}
- {% include 'post_reply.html' %}
-{% endfor %}
-
{% if hr %}
{% endif %}
-
+{% filter remove_whitespace %}
+{# tabs and new lines will be ignored #}
+
+
+
+{% if post.embed %}
+ {{ post.embed }}
+{% elseif post.file == 'deleted' %}
+
+{% elseif post.file and post.file %}
+
{% trans %}File:{% endtrans %} {{ post.file }}
+ (
+ {% if post.thumb == 'spoiler' %}
+ {% trans %}Spoiler Image{% endtrans %},
+ {% endif %}
+ {{ post.filesize|filesize }}
+ {% if post.filex and post.filey %}
+ , {{ post.filex}}x{{ post.filey }}
+ {% if config.show_ratio %}
+ , {{ post.ratio }}
+ {% endif %}
+ {% endif %}
+ {% if config.show_filename and post.filename %}
+ ,
+ {% if post.filename|length > config.max_filename_display %}
+ {{ post.filename|truncate(config.max_filename_display)|bidi_cleanup }}
+ {% else %}
+ {{ post.filename|bidi_cleanup }}
+ {% endif %}
+ {% endif %}
+ {% if post.thumb != 'file' and config.image_identification %}
+ ,
+
+ io
+ {% if post.file|extension == 'jpg' %}
+ e
+ {% endif %}
+ g
+ t
+
+ {% endif %}
+ )
+
+
+
+{% endif %}
+
+
+
+ {% if post.subject|length > 0 %}
+ {# show subject #}
+ {{ post.subject|bidi_cleanup }}
+ {% endif %}
+ {% if post.email|length > 0 %}
+ {# start email #}
+
+ {% endif %}
+ {% set capcode = post.capcode|capcode %}
+ {{ post.name|bidi_cleanup }}
+ {% if post.trip|length > 0 %}
+ {{ post.trip }}
+ {% endif %}
+ {% if post.email|length > 0 %}
+ {# end email #}
+
+ {% endif %}
+ {% if capcode %}
+ {{ capcode.cap }}
+ {% endif %}
+ {% if post.mod and post.mod|hasPermission(config.mod.show_ip, board.uri) %}
+ [{{ post.ip }} ]
+ {% endif %}
+ {{ post.time|date(config.post_date) }}
+
+ {% if config.poster_ids %}
+ ID: {{ post.ip|poster_id(post.id) }}
+ {% endif %}
+ No.
+
+ {{ post.id }}
+
+ {% if post.sticky %}
+
+ {% endif %}
+ {% if post.locked %}
+
+ {% endif %}
+ {% if post.bumplocked and (config.mod.view_bumplock < 0 or (post.mod and post.mod|hasPermission(config.mod.view_bumplock, board.uri))) %}
+
+ {% endif %}
+ {% if index %}
+ [{% trans %}Reply{% endtrans %}]
+ {% endif %}
+ {{ post.postControls }}
+
+
+ {% endfilter %}{% if index %}{{ post.body|truncate_body(post.link) }}{% else %}{{ post.body }}{% endif %}{% filter remove_whitespace %}
+
+ {% if post.omitted or post.omitted_images %}
+
+ {% if post.omitted %}
+ {% trans %}
+ 1 post
+ {% plural post.omitted %}
+ {{ count }} posts
+ {% endtrans %}
+ {% if post.omitted_images %}
+ {% trans %}and{% endtrans %}
+ {% endif %}
+ {% endif %}
+ {% if post.omitted_images %}
+ {% trans %}
+ 1 image reply
+ {% plural post.omitted_images %}
+ {{ count }} image replies
+ {% endtrans %}
+ {% endif %} {% trans %}omitted. Click reply to view.{% endtrans %}
+
+ {% endif %}
+{% if not index %}
+{% endif %}
+
{% endfilter %}
+{% set hr = post.hr %}
+{% for post in post.posts %}
+ {% include 'post_reply.html' %}
+{% endfor %}
+
{% if hr %}
{% endif %}
+
\ No newline at end of file
diff --git a/templates/themes/basic/theme.php b/templates/themes/basic/theme.php
index 806a331a..7ba0d1e7 100644
--- a/templates/themes/basic/theme.php
+++ b/templates/themes/basic/theme.php
@@ -1,7 +1,7 @@
+
+
+
+ {{ settings.title }}
+
+
+ {% if config.url_favicon %} {% endif %}
+
+
+ {{ boardlist.top }}
+
+
+ {{ settings.subtitle }}
+
+
+
+ {% for post in recent_posts %}
+
+
+
+
+
+ {% trans %}1 reply{% plural post.reply_count %}{{ count }} replies{% endtrans %}
+ {{ post.body }}
+
+
+ {% endfor %}
+
+
+
+ Powered by Tinyboard {{ config.version }} | Tinyboard Copyright © 2010-2013 Tinyboard Development Group
+
+
+{% endfilter %}
diff --git a/templates/themes/catalog/info.php b/templates/themes/catalog/info.php
new file mode 100644
index 00000000..1b9831ae
--- /dev/null
+++ b/templates/themes/catalog/info.php
@@ -0,0 +1,42 @@
+ 'Title',
+ 'name' => 'title',
+ 'type' => 'text',
+ 'default' => 'Catalog'
+ );
+
+ $__boards = listBoards();
+ $__default_boards = Array();
+ foreach ($__boards as $__board)
+ $__default_boards[] = $__board['uri'];
+
+ $theme['config'][] = Array(
+ 'title' => 'Included boards',
+ 'name' => 'boards',
+ 'type' => 'text',
+ 'comment' => '(space seperated)',
+ 'default' => implode(' ', $__default_boards)
+ );
+
+ $theme['config'][] = Array(
+ 'title' => 'CSS file',
+ 'name' => 'css',
+ 'type' => 'text',
+ 'default' => 'catalog.css',
+ 'comment' => '(eg. "catalog.css")'
+ );
+
+ // Unique function name for building everything
+ $theme['build_function'] = 'catalog_build';
diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php
new file mode 100644
index 00000000..e585cf17
--- /dev/null
+++ b/templates/themes/catalog/theme.php
@@ -0,0 +1,60 @@
+build($settings, $board);
+ }
+ } elseif ($action == 'post-thread' && in_array($board, $boards)) {
+ $b = new Catalog();
+ $b->build($settings, $board);
+ }
+ }
+
+ // Wrap functions in a class so they don't interfere with normal Tinyboard operations
+ class Catalog {
+ public function build($settings, $board_name) {
+ global $config, $board;
+
+ openBoard($board_name);
+
+ $recent_images = array();
+ $recent_posts = array();
+ $stats = array();
+
+ $query = query(sprintf("SELECT *, `id` AS `thread_id`, (SELECT COUNT(*) FROM `posts_%s` WHERE `thread` = `thread_id`) AS `reply_count`, '%s' AS `board` FROM `posts_%s` WHERE `thread` IS NULL ORDER BY `bump` DESC", $board_name, $board_name, $board_name)) or error(db_error());
+
+ while ($post = $query->fetch()) {
+ $post['link'] = $config['root'] . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], ($post['thread'] ? $post['thread'] : $post['id']));
+ $post['board_name'] = $board['name'];
+ $post['file'] = $config['uri_thumb'] . $post['thumb'];
+ $recent_posts[] = $post;
+ }
+
+ file_write($config['dir']['home'] . $board_name . '/catalog.html', Element('themes/catalog/catalog.html', Array(
+ 'settings' => $settings,
+ 'config' => $config,
+ 'boardlist' => createBoardlist(),
+ 'recent_images' => $recent_images,
+ 'recent_posts' => $recent_posts,
+ 'stats' => $stats,
+ 'board' => $board_name,
+ 'link' => $config['root'] . $board['dir']
+ )));
+ }
+ };
diff --git a/templates/themes/categories/theme.php b/templates/themes/categories/theme.php
index 11fcbbaf..7d468d0d 100644
--- a/templates/themes/categories/theme.php
+++ b/templates/themes/categories/theme.php
@@ -1,7 +1,7 @@
build($action, $settings);
@@ -23,7 +24,7 @@
$this->excluded = explode(' ', $settings['exclude']);
- if ($action == 'all' || $action == 'post')
+ if ($action == 'all' || $action == 'post' || $action == 'post-thread')
file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings));
}
diff --git a/templates/themes/rrdtool/theme.php b/templates/themes/rrdtool/theme.php
index eed2ac9a..68fc1850 100644
--- a/templates/themes/rrdtool/theme.php
+++ b/templates/themes/rrdtool/theme.php
@@ -1,7 +1,7 @@
'Sitemap Path',
+ 'name' => 'path',
+ 'type' => 'text',
+ 'default' => 'sitemap.xml',
+ 'size' => '20'
+ );
+
+ $theme['config'][] = Array(
+ 'title' => 'URL prefix',
+ 'name' => 'url',
+ 'type' => 'text',
+ 'comment' => '(with trailing slash)',
+ 'default' => 'http://' . (isset ($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : "localhost") . $config['root'],
+ 'size' => '20'
+ );
+
+ $theme['config'][] = Array(
+ 'title' => 'Thread change frequency',
+ 'name' => 'changefreq',
+ 'type' => 'text',
+ 'comment' => '(eg. "hourly", "daily", etc.)',
+ 'default' => 'hourly',
+ 'size' => '20'
+ );
+
+ $__boards = listBoards();
+ $__default_boards = Array();
+ foreach ($__boards as $__board)
+ $__default_boards[] = $__board['uri'];
+
+ $theme['config'][] = Array(
+ 'title' => 'Boards',
+ 'name' => 'boards',
+ 'type' => 'text',
+ 'comment' => '(boards to include; space seperated)',
+ 'size' => 24,
+ 'default' => implode(' ', $__default_boards)
+ );
+
+ $theme['build_function'] = 'sitemap_build';
diff --git a/templates/themes/sitemap/sitemap.xml b/templates/themes/sitemap/sitemap.xml
new file mode 100644
index 00000000..733da607
--- /dev/null
+++ b/templates/themes/sitemap/sitemap.xml
@@ -0,0 +1,19 @@
+{% filter remove_whitespace %}
+
+
+ {% for board in boards %}
+
+ {{ settings.url ~ (config.board_path | format(board)) ~ config.file_index }}
+
+ {% endfor %}
+ {% for board, thread_list in threads %}
+ {% for thread in thread_list %}
+
+ {{ settings.url ~ (config.board_path | format(board)) ~ config.dir.res ~ (config.file_page | format(thread.thread_id)) }}
+ {{ thread.lastmod | date('%Y-%m-%dT%H:%M:%S') }}{{ timezone() }}
+ {{ settings.changefreq }}
+
+ {% endfor %}
+ {% endfor %}
+
+{% endfilter %}
\ No newline at end of file
diff --git a/templates/themes/sitemap/theme.php b/templates/themes/sitemap/theme.php
new file mode 100644
index 00000000..e210316b
--- /dev/null
+++ b/templates/themes/sitemap/theme.php
@@ -0,0 +1,32 @@
+fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ file_write($settings['path'], Element('themes/sitemap/sitemap.xml', Array(
+ 'settings' => $settings,
+ 'config' => $config,
+ 'threads' => $threads,
+ 'boards' => $boards,
+ )));
+ }
diff --git a/templates/themes/ukko/info.php b/templates/themes/ukko/info.php
new file mode 100644
index 00000000..34d59d4e
--- /dev/null
+++ b/templates/themes/ukko/info.php
@@ -0,0 +1,53 @@
+ 'Board name',
+ 'name' => 'title',
+ 'type' => 'text',
+ 'default' => 'Ukko'
+ );
+ $theme['config'][] = Array(
+ 'title' => 'Board URI',
+ 'name' => 'uri',
+ 'type' => 'text',
+ 'comment' => '(ukko for example)'
+ );
+ $theme['config'][] = Array(
+ 'title' => 'Subtitle',
+ 'name' => 'subtitle',
+ 'type' => 'text',
+ 'comment' => '(%s = thread limit. for example "%s freshly bumped threads")'
+ );
+ $theme['config'][] = Array(
+ 'title' => 'Excluded boards',
+ 'name' => 'exclude',
+ 'type' => 'text',
+ 'comment' => '(space seperated)'
+ );
+ $theme['config'][] = Array(
+ 'title' => 'Number of threads',
+ 'name' => 'thread_limit',
+ 'type' => 'text',
+ 'default' => '15',
+ );
+ // Unique function name for building everything
+ $theme['build_function'] = 'ukko_build';
+ $theme['install_callback'] = 'ukko_install';
+
+ if(!function_exists('ukko_install')) {
+ function ukko_install($settings) {
+ if (!file_exists($settings['uri']))
+ @mkdir($settings['uri'], 0777) or error("Couldn't create " . $settings['uri'] . ". Check permissions.", true);
+ }
+ }
+
diff --git a/templates/themes/ukko/theme.php b/templates/themes/ukko/theme.php
new file mode 100644
index 00000000..6fa5fa7e
--- /dev/null
+++ b/templates/themes/ukko/theme.php
@@ -0,0 +1,111 @@
+settings = $settings;
+ $ukko->build();
+ }
+
+ class ukko {
+ public $settings;
+ public function build($mod = false) {
+ global $config;
+ $boards = listBoards();
+
+ $body = '';
+ $overflow = array();
+ $board = array(
+ 'url' => $this->settings['uri'],
+ 'name' => $this->settings['title'],
+ 'title' => sprintf($this->settings['subtitle'], $this->settings['thread_limit'])
+ );
+
+ $query = '';
+ foreach($boards as &$_board) {
+ if(in_array($_board['uri'], explode(' ', $this->settings['exclude'])))
+ continue;
+ $query .= sprintf("SELECT *, '%s' AS `board` FROM `posts_%s` WHERE `thread` IS NULL UNION ALL ", $_board['uri'], $_board['uri']);
+ }
+ $query = preg_replace('/UNION ALL $/', 'ORDER BY `bump` DESC', $query);
+ $query = query($query) or error(db_error());
+
+ $count = 0;
+ $threads = array();
+ while($post = $query->fetch()) {
+
+ if(!isset($threads[$post['board']])) {
+ $threads[$post['board']] = 1;
+ } else {
+ $threads[$post['board']] += 1;
+ }
+
+ if($count < $this->settings['thread_limit']) {
+ openBoard($post['board']);
+ $thread = new Thread(
+ $post['id'], $post['subject'], $post['email'], $post['name'], $post['trip'], $post['capcode'], $post['body'], $post['time'],
+ $post['thumb'], $post['thumbwidth'], $post['thumbheight'], $post['file'], $post['filewidth'], $post['fileheight'], $post['filesize'],
+ $post['filename'], $post['ip'], $post['sticky'], $post['locked'], $post['sage'], $post['embed'], $mod ? '?/' : $config['root'], $mod
+ );
+
+ $posts = prepare(sprintf("SELECT * FROM `posts_%s` WHERE `thread` = :id ORDER BY `id` DESC LIMIT :limit", $post['board']));
+ $posts->bindValue(':id', $post['id']);
+ $posts->bindValue(':limit', ($post['sticky'] ? $config['threads_preview_sticky'] : $config['threads_preview']), PDO::PARAM_INT);
+ $posts->execute() or error(db_error($posts));
+
+ $num_images = 0;
+ while ($po = $posts->fetch()) {
+ if ($po['file'])
+ $num_images++;
+
+ $thread->add(new Post(
+ $po['id'], $post['id'], $po['subject'], $po['email'], $po['name'], $po['trip'], $po['capcode'], $po['body'], $po['time'],
+ $po['thumb'], $po['thumbwidth'], $po['thumbheight'], $po['file'], $po['filewidth'], $po['fileheight'], $po['filesize'],
+ $po['filename'], $po['ip'], $po['embed'], $mod ? '?/' : $config['root'], $mod)
+ );
+
+ }
+ if ($posts->rowCount() == ($post['sticky'] ? $config['threads_preview_sticky'] : $config['threads_preview'])) {
+ $ct = prepare(sprintf("SELECT COUNT(`id`) as `num` FROM `posts_%s` WHERE `thread` = :thread UNION ALL SELECT COUNT(`id`) FROM `posts_%s` WHERE `file` IS NOT NULL AND `thread` = :thread", $post['board'], $post['board']));
+ $ct->bindValue(':thread', $post['id'], PDO::PARAM_INT);
+ $ct->execute() or error(db_error($count));
+
+ $c = $ct->fetch();
+ $thread->omitted = $c['num'] - ($post['sticky'] ? $config['threads_preview_sticky'] : $config['threads_preview']);
+
+ $c = $ct->fetch();
+ $thread->omitted_images = $c['num'] - $num_images;
+ }
+
+
+ $thread->posts = array_reverse($thread->posts);
+ $body .= '';
+ $body .= $thread->build(true);
+ } else {
+ $page = 'index';
+ if(floor($threads[$post['board']] / $config['threads_per_page']) > 0) {
+ $page = floor($threads[$post['board']] / $config['threads_per_page']) + 1;
+ }
+ $overflow[] = array('id' => $post['id'], 'board' => $post['board'], 'page' => $page . '.html');
+ }
+
+ $count += 1;
+ }
+
+ $body .= '';
+ $body .= '';
+
+ file_write($this->settings['uri'] . '/index.html', Element('index.html', array(
+ 'config' => $config,
+ 'board' => $board,
+ 'no_post_form' => true,
+ 'body' => $body,
+ 'boardlist' => createBoardlist($mod)
+ )));
+
+ file_write($this->settings['uri'] . '/ukko.js', Element('themes/ukko/ukko.js', array()));
+ }
+
+ };
+
+?>
diff --git a/templates/themes/ukko/thumb.png b/templates/themes/ukko/thumb.png
new file mode 100644
index 00000000..49c4237b
Binary files /dev/null and b/templates/themes/ukko/thumb.png differ
diff --git a/templates/themes/ukko/ukko.js b/templates/themes/ukko/ukko.js
new file mode 100644
index 00000000..baf0f78d
--- /dev/null
+++ b/templates/themes/ukko/ukko.js
@@ -0,0 +1,37 @@
+var cache = new Array(),
+ thread = false,
+ loading = false;
+$(document).ready(function() {
+ $(window).on('scroll', function() {
+ if($(window).scrollTop() + $(window).height() + 100 > $(document).height() && !loading && overflow.length > 0) {
+ var page = '../' + overflow[0].board + '/' + overflow[0].page;
+ if($.inArray(page, cache) != -1) {
+ thread = $('div#thread_' + overflow[0].id);
+ if(thread.length > 0) {
+ thread.prepend('');
+ $('div[id*="thread_"]').last().after(thread.attr('data-board', overflow[0].board).css('display', 'block'));
+ overflow.shift();
+ }
+ } else {
+ loading = true;
+ $.get(page, function(data) {
+ cache.push(page);
+
+ $(data).find('div[id*="thread_"]').each(function() {
+ $('body').prepend($(this).css('display', 'none').attr('data-board', overflow[0].board));
+ });
+
+ thread = $('div#thread_' + overflow[0].id + '[data-board="' + overflow[0].board + '"]');
+ if(thread.length > 0) {
+ thread.prepend('');
+ $('div[id*="thread_"]').last().after(thread.attr('data-board', overflow[0].board).css('display', 'block'));
+ overflow.shift();
+ }
+
+ loading = false;
+ });
+ }
+ }
+ });
+
+});
\ No newline at end of file
diff --git a/templates/thread.html b/templates/thread.html
index a78b0843..1b217c2f 100644
--- a/templates/thread.html
+++ b/templates/thread.html
@@ -2,35 +2,13 @@
-
-
- {% if config.url_favicon %} {% endif %}
- {{ board.url }} - {{ board.name }}
-
- {% if config.meta_keywords %} {% endif %}
- {% if config.default_stylesheet.1 != '' %} {% endif %}
-
- {% if not nojavascript %}
-
- {% if not config.additional_javascript_compile %}
- {% for javascript in config.additional_javascript %}{% endfor %}
- {% endif %}
- {% endif %}
- {% if config.recaptcha %}{% endif %}
+
+
+
+ {% include 'header.html' %}
+ {{ board.url }} - {% if config.thread_subject_in_title and thread.subject %}{{ thread.subject }}{% else %}{{ board.title|e }}{% endif %}
{{ boardlist.top }}
diff --git a/tools/benchmark.php b/tools/benchmark.php
new file mode 100644
index 00000000..8c97364c
--- /dev/null
+++ b/tools/benchmark.php
@@ -0,0 +1,52 @@
+#!/usr/bin/php
+resize(
+ $config['thumb_ext'] ? $config['thumb_ext'] : $extension,
+ $config['thumb_width'],
+ $config['thumb_height']
+ );
+
+ $thumb->to($out);
+ $thumb->_destroy();
+ $image->destroy();
+ }
+ $end = microtime(true);
+
+ printf("Took %.2f seconds (%.2f/second; %.2f ms)\n", $end - $start, $rate = ($count / ($end - $start)), 1000 / $rate);
+
+ unlink($out);
+}
+
+benchmark('gd');
+benchmark('imagick');
+benchmark('convert');
+
diff --git a/tools/i18n_compile.php b/tools/i18n_compile.php
new file mode 100755
index 00000000..ba193767
--- /dev/null
+++ b/tools/i18n_compile.php
@@ -0,0 +1,44 @@
+#!/usr/bin/php
+ -1,
+ 'type' => ADMIN,
+ 'username' => '?',
+ 'boards' => Array('*')
+);
+
+function get_httpd_privileges() {
+ global $config, $shell_path, $argv;
+
+ if(php_sapi_name() != 'cli')
+ die("get_httpd_privileges(): invoked from HTTP client.\n");
+
+ echo "Dropping priviledges...\n";
+
+ if(!is_writable('.'))
+ die("get_httpd_privileges(): web directory is not writable\n");
+
+ $filename = '.' . md5(rand()) . '.php';
+ $inc_filename = '.' . md5(rand()) . '.php';
+
+ echo "Copying rebuilder to web directory...\n";
+
+ // replace "/inc/cli.php" with its new filename
+ passthru("cat " . escapeshellarg($shell_path . '/' . $_SERVER['PHP_SELF']) . " | sed \"s/'\/inc\/cli\.php'/'\/{$inc_filename}'/\" > {$filename}");
+
+ $inc_header = "regs[$this->regsCounter] = $match[1];
+ $id = "<regsCounter}>>";
+ $this->regsCounter++;
+ return $id;
+ }
+ protected function _extractStrings($match) {
+ $this->strings[$this->stringsCounter] = $this->importRegExps($match[0]);
+ $id = "<stringsCounter}>>";
+ $this->stringsCounter++;
+ return $id;
+ }
+ protected function importRegExps($input) {
+ $regs = $this->regs;
+ return preg_replace_callback("#<>#", function ($match) use($regs) {
+ return $regs[$match[1]];
+ }, $input);
+ }
+
+ protected function importStrings($input) {
+ $strings = $this->strings;
+ return preg_replace_callback("#<>#", function ($match) use($strings) {
+ return $strings[$match[1]];
+ }, $input);
+ }
+
+ public function __construct($file, $keywords = '_') {
+ $this->content = file_get_contents($file);
+ $this->keywords = (array)$keywords;
+ }
+
+ public function parse() {
+ $output = $this->content; //htmlspecialchars($this->content, ENT_NOQUOTES);
+
+ // extract reg exps
+ $output = preg_replace_callback(
+ '# ( / (?: (?>[^/\\\\]++) | \\\\\\\\ | (?[^"\\\\]++) | \\\\\\\\ | (?[^'\\\\]++) | \\\\\\\\ | (?strings;
+ $output = preg_replace_callback("#<>#", function($match) use($strings) {
+ return $strings[$match[1]];
+ }, $output);
+
+ $keywords = implode('|', $this->keywords);
+
+ $strings = array();
+
+ // extract func calls
+ preg_match_all(
+ '# (?:'.$keywords.') \(\\ *" ( (?: (?>[^"\\\\]++) | \\\\\\\\ | (?[^'\\\\]++) | \\\\\\\\ | (?
diff --git a/tools/inc/lib/jsgettext/PoeditParser.php b/tools/inc/lib/jsgettext/PoeditParser.php
new file mode 100644
index 00000000..eebd816e
--- /dev/null
+++ b/tools/inc/lib/jsgettext/PoeditParser.php
@@ -0,0 +1,83 @@
+file = $file;
+ }
+
+ public function parse() {
+ $contents = file_get_contents($this->file);
+
+ $parts = preg_split('#(\r\n|\n){2}#', $contents, -1, PREG_SPLIT_NO_EMPTY);
+
+ $this->header = array_shift($parts);
+
+ foreach ($parts as $part) {
+
+ // parse comments
+ $comments = array();
+ preg_match_all('#^\\#: (.*?)$#m', $part, $matches, PREG_SET_ORDER);
+ foreach ($matches as $m) $comments[] = $m[1];
+
+ $isFuzzy = preg_match('#^\\#, fuzzy$#im', $part) ? true : false;
+
+ preg_match_all('# ^ (msgid|msgstr)\ " ( (?: (?>[^"\\\\]++) | \\\\\\\\ | (?_fixQuotes($matches2[0][2]);
+ $v = !empty($matches2[1][2]) ? $this->_fixQuotes($matches2[1][2]) : '';
+
+ $this->strings[$k] = new PoeditString($k, $v, $isFuzzy, $comments);
+ }
+ }
+
+ public function merge($strings) {
+ foreach ((array)$strings as $str) {
+ if (!in_array($str, array_keys($this->strings))) {
+ $this->strings[$str] = new PoeditString($str);
+ }
+ }
+ }
+
+ public function getHeader() {
+ return $this->header;
+ }
+
+ public function getStrings() {
+ return $this->strings;
+ }
+
+ public function getJSON() {
+ $str = array();
+ foreach ($this->strings as $s) {
+ if ($s->value) $str[$s->key] = $s->value;
+ }
+ return json_encode($str);
+ }
+
+ public function toJSON($outputFilename, $varName = 'l10n') {
+ $str = "$varName = " . $this->getJSON() . ";";
+ return file_put_contents($outputFilename, $str) !== false;
+ }
+
+ public function save($filename = null) {
+ $data = $this->header . "\n\n";
+ foreach ($this->strings as $str) {
+ $data .= $str;
+ }
+ return file_put_contents($filename ? $filename : $this->file, $data) !== false;
+ }
+}
+
+
+?>
diff --git a/tools/inc/lib/jsgettext/PoeditString.php b/tools/inc/lib/jsgettext/PoeditString.php
new file mode 100644
index 00000000..3d59d004
--- /dev/null
+++ b/tools/inc/lib/jsgettext/PoeditString.php
@@ -0,0 +1,29 @@
+key = $key;
+ $this->value = $value;
+ $this->fuzzy = $fuzzy;
+ $this->comments = (array)$comments;
+ }
+
+ public function __toString() {
+ $str ='';
+ foreach ($this->comments as $c) {
+ $str .= "#: $c\n";
+ }
+ if ($this->fuzzy) $str .= "#, fuzzy\n";
+ $str .= 'msgid "'.str_replace('"', '\\"', $this->key).'"' . "\n";
+ $str .= 'msgstr "'.str_replace('"', '\\"', $this->value).'"' . "\n";
+ $str .= "\n";
+ return $str;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/tools/inc/lib/jsgettext/jsgettext.php b/tools/inc/lib/jsgettext/jsgettext.php
new file mode 100644
index 00000000..6c70f0b4
--- /dev/null
+++ b/tools/inc/lib/jsgettext/jsgettext.php
@@ -0,0 +1,63 @@
+ array(),
+ '-o' => null,
+ '-k' => '_'
+ );
+ $len = count($args);
+ $i = 1;
+ while ($i < $len) {
+ if (preg_match('#^-[a-z]$#i', $args[$i])) {
+ $options[$args[$i]] = isset($args[$i+1]) ? trim($args[$i+1]) : true;
+ $i += 2;
+ }
+ else {
+ $options['files'][] = $args[$i];
+ $i++;
+ }
+ }
+ return $options;
+ }
+
+ $options = buildOptions($argv);
+
+ if (!file_exists($options['-o'])) {
+ touch($options['-o']);
+ }
+
+ if (!is_writable($options['-o'])) {
+ die("Invalid output file name. Make sure it exists and is writable.");
+ }
+
+ $inputFiles = $options['files'];
+
+ if (empty($inputFiles)) {
+ die("You did not provide any input file.");
+ }
+
+ $poeditParser = new PoeditParser($options['-o']);
+ $poeditParser->parse();
+
+ $errors = array();
+
+ foreach ($inputFiles as $f) {
+ if (!is_readable($f) || !preg_match('#\.js$#', $f)) {
+ $errors[] = ("$f is not a valid javascript file.");
+ continue;
+ }
+ $jsparser = new JSParser($f, explode(' ', $options['-k']));
+ $jsStrings = $jsparser->parse();
+ $poeditParser->merge($jsStrings);
+ }
+
+ if (!empty($errors)) {
+ echo "\nThe following errors occured:\n" . implode("\n", $errors) . "\n";
+ }
+
+ $poeditParser->save();
+?>
diff --git a/tools/inc/lib/jsgettext/po2json.php b/tools/inc/lib/jsgettext/po2json.php
new file mode 100644
index 00000000..847b0899
--- /dev/null
+++ b/tools/inc/lib/jsgettext/po2json.php
@@ -0,0 +1,42 @@
+ null,
+ '-i' => null,
+ '-n' => 'l10n'
+ );
+ $len = count($args);
+ $i = 0;
+ while ($i < $len) {
+ if (preg_match('#^-[a-z]$#i', $args[$i])) {
+ $options[$args[$i]] = isset($args[$i+1]) ? trim($args[$i+1]) : true;
+ $i += 2;
+ }
+ else {
+ $options[] = $args[$i];
+ $i++;
+ }
+ }
+ return $options;
+ }
+
+ $options = buildOptions($argv);
+
+ if (!file_exists($options['-i']) || !is_readable($options['-i'])) {
+ die("Invalid input file. Make sure it exists and is readable.");
+ }
+
+ $poeditParser = new PoeditParser($options['-i']);
+ $poeditParser->parse();
+
+ if ($poeditParser->toJSON($options['-o'], $options['-n'])) {
+ $strings = count($poeditParser->getStrings());
+ echo "Successfully exported " . count($strings) . " strings.\n";
+ }
+ else {
+ echo "Cannor write to file '{$options['-o']}'.\n";
+ }
+?>
diff --git a/tools/rebuild.php b/tools/rebuild.php
new file mode 100755
index 00000000..3b5c0de2
--- /dev/null
+++ b/tools/rebuild.php
@@ -0,0 +1,105 @@
+#!/usr/bin/php
+
+ * Rebuild only the specified board.
+ *
+ * -f, --full
+ * Rebuild replies as well as threads (re-markup).
+ *
+ */
+
+require dirname(__FILE__) . '/inc/cli.php';
+
+if(!is_writable($config['file_script'])) {
+ get_httpd_privileges();
+}
+
+$start = microtime(true);
+
+// parse command line
+$opts = getopt('qfb:', Array('board:', 'quick', 'full', 'quiet'));
+$options = Array();
+
+$options['board'] = isset($opts['board']) ? $opts['board'] : (isset($opts['b']) ? $opts['b'] : false);
+$options['quiet'] = isset($opts['q']) || isset($opts['quiet']);
+$options['quick'] = isset($opts['quick']);
+$options['full'] = isset($opts['full']) || isset($opts['f']);
+
+if(!$options['quiet'])
+ echo "== Tinyboard {$config['version']} ==\n";
+
+if(!$options['quiet'])
+ echo "Clearing template cache...\n";
+
+load_twig();
+$twig->clearCacheFiles();
+
+if(!$options['quiet'])
+ echo "Regenerating theme files...\n";
+rebuildThemes('all');
+
+if(!$options['quiet'])
+ echo "Generating Javascript file...\n";
+buildJavascript();
+
+$main_js = $config['file_script'];
+
+$boards = listBoards();
+
+foreach($boards as &$board) {
+ if($options['board'] && $board['uri'] != $options['board'])
+ continue;
+
+ if(!$options['quiet'])
+ echo "Opening board /{$board['uri']}/...\n";
+ openBoard($board['uri']);
+
+ if($config['file_script'] != $main_js) {
+ // different javascript file
+ if(!$options['quiet'])
+ echo "Generating Javascript file...\n";
+ buildJavascript();
+ }
+
+
+ if(!$options['quiet'])
+ echo "Creating index pages...\n";
+ buildIndex();
+
+ if($options['quick'])
+ continue; // do no more
+
+ if($options['full']) {
+ $query = query(sprintf("SELECT `id` FROM `posts_%s`", $board['uri'])) or error(db_error());
+ while($post = $query->fetch()) {
+ if(!$options['quiet'])
+ echo "Rebuilding #{$post['id']}...\n";
+ rebuildPost($post['id']);
+ }
+ }
+
+ $query = query(sprintf("SELECT `id` FROM `posts_%s` WHERE `thread` IS NULL", $board['uri'])) or error(db_error());
+ while($post = $query->fetch()) {
+ if(!$options['quiet'])
+ echo "Rebuilding #{$post['id']}...\n";
+ buildThread($post['id']);
+ }
+}
+
+if(!$options['quiet'])
+ printf("Complete! Took %g seconds\n", microtime(true) - $start);
+
+unset($board);
+modLog('Rebuilt everything using tools/rebuild.php');
+