summaryrefslogtreecommitdiff
path: root/inc
diff options
context:
space:
mode:
Diffstat (limited to 'inc')
-rw-r--r--inc/api.php49
-rw-r--r--inc/config.php65
-rw-r--r--inc/display.php138
-rw-r--r--inc/functions.php127
-rw-r--r--inc/lib/Twig/Extensions/Extension/Tinyboard.php33
-rwxr-xr-xinc/lib/minify/JSMin.php226
-rwxr-xr-xinc/lib/minify/Minify.php29
-rwxr-xr-xinc/lib/minify/Minify/Build.php2
-rwxr-xr-xinc/lib/minify/Minify/CSS.php22
-rwxr-xr-xinc/lib/minify/Minify/CSS/UriRewriter.php11
-rwxr-xr-xinc/lib/minify/Minify/Cache/File.php4
-rwxr-xr-xinc/lib/minify/Minify/Controller/Base.php28
-rwxr-xr-xinc/lib/minify/Minify/Controller/Files.php2
-rwxr-xr-xinc/lib/minify/Minify/Controller/Groups.php2
-rwxr-xr-xinc/lib/minify/Minify/Controller/MinApp.php19
-rwxr-xr-xinc/lib/minify/Minify/Controller/Page.php19
-rwxr-xr-xinc/lib/minify/Minify/Controller/Version1.php11
-rwxr-xr-xinc/lib/minify/Minify/HTML.php53
-rwxr-xr-xinc/lib/minify/Minify/HTML/Helper.php54
-rwxr-xr-xinc/lib/minify/Minify/JS/ClosureCompiler.php179
-rwxr-xr-xinc/lib/minify/Minify/Lines.php10
-rwxr-xr-xinc/lib/minify/Minify/YUICompressor.php20
-rwxr-xr-xinc/lib/minify/MrClay/Cli.php33
-rwxr-xr-xinc/lib/minify/MrClay/Cli/Arg.php6
-rw-r--r--inc/lib/webm/posthandler.php30
-rw-r--r--inc/mod/pages.php176
26 files changed, 756 insertions, 592 deletions
diff --git a/inc/api.php b/inc/api.php
index 3337c613..a8ae9eed 100644
--- a/inc/api.php
+++ b/inc/api.php
@@ -26,12 +26,6 @@ class Api {
'trip' => 'trip',
'capcode' => 'capcode',
'time' => 'time',
- 'thumbheight' => 'tn_w',
- 'thumbwidth' => 'tn_h',
- 'fileheight' => 'w',
- 'filewidth' => 'h',
- 'filesize' => 'fsize',
- 'filename' => 'filename',
'omitted' => 'omitted_posts',
'omitted_images' => 'omitted_images',
'replies' => 'replies',
@@ -46,6 +40,15 @@ class Api {
'bump' => 'last_modified'
);
+ $this->fileFields = array(
+ 'thumbheight' => 'tn_w',
+ 'thumbwidth' => 'tn_h',
+ 'height' => 'w',
+ 'width' => 'h',
+ 'size' => 'fsize',
+ 'file' => 'filename',
+ );
+
if (isset($config['api']['extra_fields']) && gettype($config['api']['extra_fields']) == 'array'){
$this->postFields = array_merge($this->postFields, $config['api']['extra_fields']);
}
@@ -67,30 +70,26 @@ class Api {
'last_modified' => 1
);
- private function translatePost($post, $threadsPage = false) {
- $apiPost = array();
- $fields = $threadsPage ? $this->threadsPageFields : $this->postFields;
+ private function translateFields($fields, $object, &$apiPost) {
foreach ($fields as $local => $translated) {
- if (!isset($post->$local))
+ if (!isset($object->$local))
continue;
$toInt = isset(self::$ints[$translated]);
- $val = $post->$local;
+ $val = $object->$local;
if ($val !== null && $val !== '') {
$apiPost[$translated] = $toInt ? (int) $val : $val;
}
}
+ }
- if ($threadsPage) return $apiPost;
+ private function translatePost($post, $threadsPage = false) {
+ $apiPost = array();
+ $fields = $threadsPage ? $this->threadsPageFields : $this->postFields;
+ $this->translateFields($fields, $post, $apiPost);
- if (isset($post->filename)) {
- $dotPos = strrpos($post->filename, '.');
- $apiPost['filename'] = substr($post->filename, 0, $dotPos);
- $apiPost['ext'] = substr($post->filename, $dotPos);
- $dotPos = strrpos($post->file, '.');
- $apiPost['tim'] = substr($post->file, 0, $dotPos);
- }
+ if ($threadsPage) return $apiPost;
// Handle country field
if (isset($post->body_nomarkup) && $this->config['country_flags']) {
@@ -104,6 +103,18 @@ class Api {
}
}
+ // Handle files
+ // Note: 4chan only supports one file, so only the first file is taken into account for 4chan-compatible API.
+ if (isset($post->files) && $post->files && !$threadsPage) {
+ $file = $post->files[0];
+ $this->translateFields($this->fileFields, $file, $apiPost);
+ $dotPos = strrpos($file->file, '.');
+ $apiPost['filename'] = substr($file->file, 0, $dotPos);
+ $apiPost['ext'] = substr($file->file, $dotPos);
+ $dotPos = strrpos($file->file, '.');
+ $apiPost['tim'] = substr($file->file, 0, $dotPos);
+ }
+
return $apiPost;
}
diff --git a/inc/config.php b/inc/config.php
index 416209b7..494ccda1 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -270,7 +270,6 @@
'recaptcha_challenge_field',
'recaptcha_response_field',
'spoiler',
- 'quick-reply',
'page',
'file_url',
'json_response',
@@ -451,6 +450,8 @@
// Maximum filename length to display (the rest can be viewed upon mouseover).
$config['max_filename_display'] = 30;
+ // Allow users to delete their own posts?
+ $config['allow_delete'] = true;
// How long after posting should you have to wait before being able to delete that post? (In seconds.)
$config['delete_time'] = 10;
// Reply limit (stops bumping thread when this is reached).
@@ -607,6 +608,17 @@
* Image settings
* ====================
*/
+ // Maximum number of images allowed. Increasing this number enabled multi image.
+ // If you make it more than 1, make sure to enable the below script for the post form to change.
+ // $config['additional_javascript'][] = 'js/multi_image.js';
+ $config['max_images'] = 1;
+
+ // Method to use for determing the max filesize.
+ // "split" means that your max filesize is split between the images. For example, if your max filesize
+ // is 2MB, the filesizes of all files must add up to 2MB for it to work.
+ // "each" means that each file can be 2MB, so if your max_images is 3, each post could contain 6MB of
+ // images. "split" is recommended.
+ $config['multiimage_method'] = 'split';
// For resizing, maximum thumbnail dimensions.
$config['thumb_width'] = 255;
@@ -628,22 +640,22 @@
/*
* Thumbnailing method:
*
- * 'gd' PHP GD (default). Only handles the most basic image formats (GIF, JPEG, PNG).
- * GD is a prerequisite for Tinyboard no matter what method you choose.
+ * 'gd' PHP GD (default). Only handles the most basic image formats (GIF, JPEG, PNG).
+ * GD is a prerequisite for Tinyboard no matter what method you choose.
*
- * 'imagick' PHP's ImageMagick bindings. Fast and efficient, supporting many image formats.
- * A few minor bugs. http://pecl.php.net/package/imagick
+ * 'imagick' PHP's ImageMagick bindings. Fast and efficient, supporting many image formats.
+ * A few minor bugs. http://pecl.php.net/package/imagick
*
- * 'convert' The command line version of ImageMagick (`convert`). Fixes most of the bugs in
- * PHP Imagick. `convert` produces the best still thumbnails and is highly recommended.
+ * 'convert' The command line version of ImageMagick (`convert`). Fixes most of the bugs in
+ * PHP Imagick. `convert` produces the best still thumbnails and is highly recommended.
*
- * 'gm' GraphicsMagick (`gm`) is a fork of ImageMagick with many improvements. It is more
- * efficient and gets thumbnailing done using fewer resources.
+ * 'gm' GraphicsMagick (`gm`) is a fork of ImageMagick with many improvements. It is more
+ * efficient and gets thumbnailing done using fewer resources.
*
* 'convert+gifscale'
- * OR 'gm+gifsicle' Same as above, with the exception of using `gifsicle` (command line application)
- * instead of `convert` for resizing GIFs. It's faster and resulting animated
- * thumbnails have less artifacts than if resized with ImageMagick.
+ * OR 'gm+gifsicle' Same as above, with the exception of using `gifsicle` (command line application)
+ * instead of `convert` for resizing GIFs. It's faster and resulting animated
+ * thumbnails have less artifacts than if resized with ImageMagick.
*/
$config['thumb_method'] = 'gd';
// $config['thumb_method'] = 'convert';
@@ -693,7 +705,7 @@
// An alternative function for generating image filenames, instead of the default UNIX timestamp.
// $config['filename_func'] = function($post) {
- // return sprintf("%s", time() . substr(microtime(), 2, 3));
+ // return sprintf("%s", time() . substr(microtime(), 2, 3));
// };
// Thumbnail to use for the non-image file uploads.
@@ -730,8 +742,12 @@
// Display the file's original filename.
$config['show_filename'] = true;
- // Display image identification links using regex.info/exif, TinEye and Google Images.
+ // Display image identification links using ImgOps, regex.info/exif and Google Images.
$config['image_identification'] = false;
+ // Which of the identification links to display. Only works if $config['image_identification'] is true.
+ $config['image_identification_imgops'] = true;
+ $config['image_identification_exif'] = true;
+ $config['image_identification_google'] = true;
// Number of posts in a "View Last X Posts" page
$config['noko50_count'] = 50;
@@ -763,11 +779,6 @@
// Number of reports you can create at once.
$config['report_limit'] = 3;
- // Attention Whoring Bar
- // REMEMBER TO CHMOD attentionbar.txt PROPERLY
- // Oh, and add jQuery in additional_javascript.
- $config['attention_bar'] = false;
-
// Allow unfiltered HTML in board subtitle. This is useful for placing icons and links.
$config['allow_subtitle_html'] = false;
@@ -928,9 +939,6 @@
// Minify Javascript using http://code.google.com/p/minify/.
$config['minify_js'] = false;
- // Allows js/quick-reply-old.js to work. This could make your imageboard more vulnerable to flood attacks.
- $config['quick_reply'] = false;
-
/*
* ====================
* Video embedding
@@ -986,6 +994,7 @@
$config['error']['toolong_body'] = _('The body was too long.');
$config['error']['tooshort_body'] = _('The body was too short or empty.');
$config['error']['noimage'] = _('You must upload an image.');
+ $config['error']['toomanyimages'] = _('You have attempted to upload too many images!');
$config['error']['nomove'] = _('The server failed to handle your upload.');
$config['error']['fileext'] = _('Unsupported image format.');
$config['error']['noboard'] = _('Invalid board!');
@@ -1399,6 +1408,8 @@
$config['mod']['view_ban_appeals'] = MOD;
// Accept and deny ban appeals
$config['mod']['ban_appeals'] = MOD;
+ // View the recent posts page
+ $config['mod']['recent'] = MOD;
// Config editor permissions
$config['mod']['config'] = array();
@@ -1447,16 +1458,16 @@
$config['search']['enable'] = false;
// Maximal number of queries per IP address per minutes
- $config['search']['queries_per_minutes'] = Array(15, 2);
+ $config['search']['queries_per_minutes'] = Array(15, 2);
// Global maximal number of queries per minutes
- $config['search']['queries_per_minutes_all'] = Array(50, 2);
+ $config['search']['queries_per_minutes_all'] = Array(50, 2);
// Limit of search results
- $config['search']['search_limit'] = 100;
-
+ $config['search']['search_limit'] = 100;
+
// Boards for searching
- //$config['search']['boards'] = array('a', 'b', 'c', 'd', 'e');
+ //$config['search']['boards'] = array('a', 'b', 'c', 'd', 'e');
/*
* ====================
diff --git a/inc/display.php b/inc/display.php
index 4e8ab8fb..e5ce1da1 100644
--- a/inc/display.php
+++ b/inc/display.php
@@ -96,7 +96,7 @@ function error($message, $priority = true, $debug_stuff = false) {
// Return the bad request header, necessary for AJAX posts
// czaks: is it really so? the ajax errors only work when this is commented out
- // better yet use it when ajax is disabled
+ // better yet use it when ajax is disabled
if (!isset ($_POST['json_response'])) {
header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
}
@@ -352,6 +352,9 @@ class Post {
foreach ($post as $key => $value) {
$this->{$key} = $value;
}
+
+ if (isset($this->files))
+ $this->files = json_decode($this->files);
$this->subject = utf8tohtml($this->subject);
$this->name = utf8tohtml($this->name);
@@ -382,64 +385,11 @@ class Post {
return $this->root . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $this->thread) . '#' . $pre . $this->id;
}
- public function postControls() {
- global $board, $config;
-
- $built = '';
- if ($this->mod) {
- // Mod controls (on posts)
-
- // Delete
- if (hasPermission($config['mod']['delete'], $board['uri'], $this->mod))
- $built .= ' ' . secure_link_confirm($config['mod']['link_delete'], 'Delete', 'Are you sure you want to delete this?', $board['dir'] . 'delete/' . $this->id);
-
- // Delete all posts by IP
- if (hasPermission($config['mod']['deletebyip'], $board['uri'], $this->mod))
- $built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip'], 'Delete all posts by IP', 'Are you sure you want to delete all posts by this IP address?', $board['dir'] . 'deletebyip/' . $this->id);
-
- // Delete all posts by IP (global)
- if (hasPermission($config['mod']['deletebyip_global'], $board['uri'], $this->mod))
- $built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip_global'], 'Delete all posts by IP across all boards', 'Are you sure you want to delete all posts by this IP address, across all boards?', $board['dir'] . 'deletebyip/' . $this->id . '/global');
-
- // Ban
- if (hasPermission($config['mod']['ban'], $board['uri'], $this->mod))
- $built .= ' <a title="'._('Ban').'" href="?/' . $board['dir'] . 'ban/' . $this->id . '">' . $config['mod']['link_ban'] . '</a>';
-
- // Ban & Delete
- if (hasPermission($config['mod']['bandelete'], $board['uri'], $this->mod))
- $built .= ' <a title="'._('Ban & Delete').'" href="?/' . $board['dir'] . 'ban&amp;delete/' . $this->id . '">' . $config['mod']['link_bandelete'] . '</a>';
-
- // Delete file (keep post)
- if (!empty($this->file) && hasPermission($config['mod']['deletefile'], $board['uri'], $this->mod))
- $built .= ' ' . secure_link_confirm($config['mod']['link_deletefile'], _('Delete file'), _('Are you sure you want to delete this file?'), $board['dir'] . 'deletefile/' . $this->id);
-
- // Spoiler file (keep post)
- if (!empty($this->file) && $this->file != "deleted" && $this->file != null && $this->thumb != 'spoiler' && hasPermission($config['mod']['spoilerimage'], $board['uri'], $this->mod) && $config['spoiler_images'])
- $built .= ' ' . secure_link_confirm($config['mod']['link_spoilerimage'], _('Spoiler File'), _('Are you sure you want to spoiler this file?'), $board['uri'] . '/spoiler/' . $this->id);
-
- // Move post
- if (hasPermission($config['mod']['move'], $board['uri'], $this->mod) && $config['move_replies'])
- $built .= ' <a title="'._('Move reply to another board').'" href="?/' . $board['uri'] . '/move_reply/' . $this->id . '">' . $config['mod']['link_move'] . '</a>';
-
- // Edit post
- if (hasPermission($config['mod']['editpost'], $board['uri'], $this->mod))
- $built .= ' <a title="'._('Edit post').'" href="?/' . $board['dir'] . 'edit' . ($config['mod']['raw_html_default'] ? '_raw' : '') . '/' . $this->id . '">' . $config['mod']['link_editpost'] . '</a>';
-
-
- if (!empty($built))
- $built = '<span class="controls">' . $built . '</span>';
- }
- return $built;
- }
-
- public function ratio() {
- return fraction($this->filewidth, $this->fileheight, ':');
- }
public function build($index=false) {
global $board, $config;
- return Element('post_reply.html', array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index));
+ return Element('post_reply.html', array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'mod' => $this->mod));
}
};
@@ -453,6 +403,9 @@ class Thread {
$this->{$key} = $value;
}
+ if (isset($this->files))
+ $this->files = json_decode($this->files);
+
$this->subject = utf8tohtml($this->subject);
$this->name = utf8tohtml($this->name);
$this->mod = $mod;
@@ -491,79 +444,8 @@ class Thread {
$this->posts[] = $post;
}
public function postCount() {
- return count($this->posts) + $this->omitted;
+ return count($this->posts) + $this->omitted;
}
- public function postControls() {
- global $board, $config;
-
- $built = '';
- if ($this->mod) {
- // Mod controls (on posts)
- // Delete
- if (hasPermission($config['mod']['delete'], $board['uri'], $this->mod))
- $built .= ' ' . secure_link_confirm($config['mod']['link_delete'], _('Delete'), _('Are you sure you want to delete this?'), $board['dir'] . 'delete/' . $this->id);
-
- // Delete all posts by IP
- if (hasPermission($config['mod']['deletebyip'], $board['uri'], $this->mod))
- $built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip'], _('Delete all posts by IP'), _('Are you sure you want to delete all posts by this IP address?'), $board['dir'] . 'deletebyip/' . $this->id);
-
- // Delete all posts by IP (global)
- if (hasPermission($config['mod']['deletebyip_global'], $board['uri'], $this->mod))
- $built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip_global'], _('Delete all posts by IP across all boards'), _('Are you sure you want to delete all posts by this IP address, across all boards?'), $board['dir'] . 'deletebyip/' . $this->id . '/global');
-
- // Ban
- if (hasPermission($config['mod']['ban'], $board['uri'], $this->mod))
- $built .= ' <a title="'._('Ban').'" href="?/' . $board['dir'] . 'ban/' . $this->id . '">' . $config['mod']['link_ban'] . '</a>';
-
- // Ban & Delete
- if (hasPermission($config['mod']['bandelete'], $board['uri'], $this->mod))
- $built .= ' <a title="'._('Ban & Delete').'" href="?/' . $board['dir'] . 'ban&amp;delete/' . $this->id . '">' . $config['mod']['link_bandelete'] . '</a>';
-
- // Delete file (keep post)
- if (!empty($this->file) && $this->file != 'deleted' && hasPermission($config['mod']['deletefile'], $board['uri'], $this->mod))
- $built .= ' ' . secure_link_confirm($config['mod']['link_deletefile'], _('Delete file'), _('Are you sure you want to delete this file?'), $board['dir'] . 'deletefile/' . $this->id);
-
- // Spoiler file (keep post)
- if (!empty($this->file) && $this->file != "deleted" && $this->file != null && $this->thumb != 'spoiler' && hasPermission($config['mod']['spoilerimage'], $board['uri'], $this->mod) && $config['spoiler_images'])
- $built .= ' ' . secure_link_confirm($config['mod']['link_spoilerimage'], _('Spoiler File'), _('Are you sure you want to spoiler this file?'), $board['uri'] . '/spoiler/' . $this->id);
-
- // Sticky
- if (hasPermission($config['mod']['sticky'], $board['uri'], $this->mod))
- if ($this->sticky)
- $built .= ' <a title="'._('Make thread not sticky').'" href="?/' . secure_link($board['dir'] . 'unsticky/' . $this->id) . '">' . $config['mod']['link_desticky'] . '</a>';
- else
- $built .= ' <a title="'._('Make thread sticky').'" href="?/' . secure_link($board['dir'] . 'sticky/' . $this->id) . '">' . $config['mod']['link_sticky'] . '</a>';
-
- if (hasPermission($config['mod']['bumplock'], $board['uri'], $this->mod))
- if ($this->sage)
- $built .= ' <a title="'._('Allow thread to be bumped').'" href="?/' . secure_link($board['dir'] . 'bumpunlock/' . $this->id) . '">' . $config['mod']['link_bumpunlock'] . '</a>';
- else
- $built .= ' <a title="'._('Prevent thread from being bumped').'" href="?/' . secure_link($board['dir'] . 'bumplock/' . $this->id) . '">' . $config['mod']['link_bumplock'] . '</a>';
-
- // Lock
- if (hasPermission($config['mod']['lock'], $board['uri'], $this->mod))
- if ($this->locked)
- $built .= ' <a title="'._('Unlock thread').'" href="?/' . secure_link($board['dir'] . 'unlock/' . $this->id) . '">' . $config['mod']['link_unlock'] . '</a>';
- else
- $built .= ' <a title="'._('Lock thread').'" href="?/' . secure_link($board['dir'] . 'lock/' . $this->id) . '">' . $config['mod']['link_lock'] . '</a>';
-
- if (hasPermission($config['mod']['move'], $board['uri'], $this->mod))
- $built .= ' <a title="'._('Move thread to another board').'" href="?/' . $board['dir'] . 'move/' . $this->id . '">' . $config['mod']['link_move'] . '</a>';
-
- // Edit post
- if (hasPermission($config['mod']['editpost'], $board['uri'], $this->mod))
- $built .= ' <a title="'._('Edit post').'" href="?/' . $board['dir'] . 'edit' . ($config['mod']['raw_html_default'] ? '_raw' : '') . '/' . $this->id . '">' . $config['mod']['link_editpost'] . '</a>';
-
- if (!empty($built))
- $built = '<span class="controls op">' . $built . '</span>';
- }
- return $built;
- }
-
- public function ratio() {
- return fraction($this->filewidth, $this->fileheight, ':');
- }
-
public function build($index=false, $isnoko50=false) {
global $board, $config, $debug;
@@ -571,7 +453,7 @@ class Thread {
event('show-thread', $this);
- $built = Element('post_thread.html', array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'hasnoko50' => $hasnoko50, 'isnoko50' => $isnoko50));
+ $built = Element('post_thread.html', array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'hasnoko50' => $hasnoko50, 'isnoko50' => $isnoko50, 'mod' => $this->mod));
return $built;
}
diff --git a/inc/functions.php b/inc/functions.php
index efc0594b..db9c91b0 100644
--- a/inc/functions.php
+++ b/inc/functions.php
@@ -96,9 +96,9 @@ function loadConfig() {
$configstr = file_get_contents('inc/instance-config.php');
- if (isset($board['dir']) && file_exists($board['dir'] . '/config.php')) {
- $configstr .= file_get_contents($board['dir'] . '/config.php');
- }
+ if (isset($board['dir']) && file_exists($board['dir'] . '/config.php')) {
+ $configstr .= file_get_contents($board['dir'] . '/config.php');
+ }
$matches = array();
preg_match_all('/[^\/*#]\$config\s*\[\s*[\'"]locale[\'"]\s*\]\s*=\s*([\'"])(.*?)\1/', $configstr, $matches);
if ($matches && isset ($matches[2]) && $matches[2]) {
@@ -691,22 +691,15 @@ function displayBan($ban) {
}
$ban['ip'] = $_SERVER['REMOTE_ADDR'];
+
if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
if (openBoard($ban['post']['board'])) {
-
- $query = query(sprintf("SELECT `thumb`, `file` FROM ``posts_%s`` WHERE `id` = " .
+ $query = query(sprintf("SELECT `files` FROM ``posts_%s`` WHERE `id` = " .
(int)$ban['post']['id'], $board['uri']));
if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
$ban['post'] = array_merge($ban['post'], $_post);
- } else {
- $ban['post']['file'] = 'deleted';
- $ban['post']['thumb'] = false;
}
- } else {
- $ban['post']['file'] = 'deleted';
- $ban['post']['thumb'] = false;
}
-
if ($ban['post']['thread']) {
$post = new Post($ban['post']);
} else {
@@ -861,7 +854,7 @@ function insertFloodPost(array $post) {
function post(array $post) {
global $pdo, $board;
- $query = prepare(sprintf("INSERT INTO ``posts_%s`` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :thumb, :thumbwidth, :thumbheight, :file, :width, :height, :filesize, :filename, :filehash, :password, :ip, :sticky, :locked, 0, :embed)", $board['uri']));
+ $query = prepare(sprintf("INSERT INTO ``posts_%s`` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :files, :num_files, :filehash, :password, :ip, :sticky, :locked, 0, :embed)", $board['uri']));
// Basic stuff
if (!empty($post['subject'])) {
@@ -921,31 +914,12 @@ function post(array $post) {
}
if ($post['has_file']) {
- $query->bindValue(':thumb', $post['thumb']);
- $query->bindValue(':thumbwidth', $post['thumbwidth'], PDO::PARAM_INT);
- $query->bindValue(':thumbheight', $post['thumbheight'], PDO::PARAM_INT);
- $query->bindValue(':file', $post['file']);
-
- if (isset($post['width'], $post['height'])) {
- $query->bindValue(':width', $post['width'], PDO::PARAM_INT);
- $query->bindValue(':height', $post['height'], PDO::PARAM_INT);
- } else {
- $query->bindValue(':width', null, PDO::PARAM_NULL);
- $query->bindValue(':height', null, PDO::PARAM_NULL);
- }
-
- $query->bindValue(':filesize', $post['filesize'], PDO::PARAM_INT);
- $query->bindValue(':filename', $post['filename']);
+ $query->bindValue(':files', json_encode($post['files']));
+ $query->bindValue(':num_files', $post['num_files']);
$query->bindValue(':filehash', $post['filehash']);
} else {
- $query->bindValue(':thumb', null, PDO::PARAM_NULL);
- $query->bindValue(':thumbwidth', null, PDO::PARAM_NULL);
- $query->bindValue(':thumbheight', null, PDO::PARAM_NULL);
- $query->bindValue(':file', null, PDO::PARAM_NULL);
- $query->bindValue(':width', null, PDO::PARAM_NULL);
- $query->bindValue(':height', null, PDO::PARAM_NULL);
- $query->bindValue(':filesize', null, PDO::PARAM_NULL);
- $query->bindValue(':filename', null, PDO::PARAM_NULL);
+ $query->bindValue(':files', null, PDO::PARAM_NULL);
+ $query->bindValue(':num_files', 0);
$query->bindValue(':filehash', null, PDO::PARAM_NULL);
}
@@ -973,33 +947,40 @@ function bumpThread($id) {
}
// Remove file from post
-function deleteFile($id, $remove_entirely_if_already=true) {
+function deleteFile($id, $remove_entirely_if_already=true, $file=null) {
global $board, $config;
- $query = prepare(sprintf("SELECT `thread`,`thumb`,`file` FROM ``posts_%s`` WHERE `id` = :id LIMIT 1", $board['uri']));
+ $query = prepare(sprintf("SELECT `thread`, `files`, `num_files` FROM ``posts_%s`` WHERE `id` = :id LIMIT 1", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if (!$post = $query->fetch(PDO::FETCH_ASSOC))
error($config['error']['invalidpost']);
+ $files = json_decode($post['files']);
+ $file_to_delete = $file !== false ? $files[(int)$file] : (object)array('file' => false);
- if ($post['file'] == 'deleted' && !$post['thread'])
+ if ($files[0]->file == 'deleted' && $post['num_files'] == 1 && !$post['thread'])
return; // Can't delete OP's image completely.
- $query = prepare(sprintf("UPDATE ``posts_%s`` SET `thumb` = NULL, `thumbwidth` = NULL, `thumbheight` = NULL, `filewidth` = NULL, `fileheight` = NULL, `filesize` = NULL, `filename` = NULL, `filehash` = NULL, `file` = :file WHERE `id` = :id", $board['uri']));
- if ($post['file'] == 'deleted' && $remove_entirely_if_already) {
+ $query = prepare(sprintf("UPDATE ``posts_%s`` SET `files` = :file WHERE `id` = :id", $board['uri']));
+ if (($file && $file_to_delete->file == 'deleted') && $remove_entirely_if_already) {
// Already deleted; remove file fully
- $query->bindValue(':file', null, PDO::PARAM_NULL);
+ $files[$file] = null;
} else {
- // Delete thumbnail
- file_unlink($board['dir'] . $config['dir']['thumb'] . $post['thumb']);
-
- // Delete file
- file_unlink($board['dir'] . $config['dir']['img'] . $post['file']);
+ foreach ($files as $i => $f) {
+ if (($file !== false && $i == $file) || $file === null) {
+ // Delete thumbnail
+ file_unlink($board['dir'] . $config['dir']['thumb'] . $f->thumb);
+ unset($files[$i]->thumb);
- // Set file to 'deleted'
- $query->bindValue(':file', 'deleted', PDO::PARAM_INT);
+ // Delete file
+ file_unlink($board['dir'] . $config['dir']['img'] . $f->file);
+ $files[$i]->file = 'deleted';
+ }
+ }
}
+ $query->bindValue(':file', json_encode($files), PDO::PARAM_STR);
+
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
@@ -1037,7 +1018,7 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
global $board, $config;
// Select post and replies (if thread) in one query
- $query = prepare(sprintf("SELECT `id`,`thread`,`thumb`,`file` FROM ``posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
+ $query = prepare(sprintf("SELECT `id`,`thread`,`files` FROM ``posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
@@ -1067,13 +1048,14 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
// Rebuild thread
$rebuild = &$post['thread'];
}
- if ($post['thumb']) {
- // Delete thumbnail
- file_unlink($board['dir'] . $config['dir']['thumb'] . $post['thumb']);
- }
- if ($post['file']) {
+ if ($post['files']) {
// Delete file
- file_unlink($board['dir'] . $config['dir']['img'] . $post['file']);
+ foreach (json_decode($post['files']) as $i => $f) {
+ if ($f->file !== 'deleted') {
+ file_unlink($board['dir'] . $config['dir']['img'] . $f->file);
+ file_unlink($board['dir'] . $config['dir']['thumb'] . $f->thumb);
+ }
+ }
}
$ids[] = (int)$post['id'];
@@ -1190,8 +1172,8 @@ function index($page, $mod=false) {
$num_images = 0;
foreach ($replies as $po) {
- if ($po['file'])
- $num_images++;
+ if ($po['num_files'])
+ $num_images+=$po['num_files'];
$thread->add(new Post($po, $mod ? '?/' : $config['root'], $mod));
}
@@ -1214,7 +1196,7 @@ function index($page, $mod=false) {
'post_url' => $config['post_url'],
'config' => $config,
'boardlist' => createBoardlist($mod),
- 'threads' => $threads
+ 'threads' => $threads,
);
}
@@ -1349,7 +1331,7 @@ function checkRobot($body) {
// Returns an associative array with 'replies' and 'images' keys
function numPosts($id) {
global $board;
- $query = prepare(sprintf("SELECT COUNT(*) AS `replies`, COUNT(NULLIF(`file`, 0)) AS `images` FROM ``posts_%s`` WHERE `thread` = :thread", $board['uri'], $board['uri']));
+ $query = prepare(sprintf("SELECT COUNT(*) AS `replies`, SUM(`num_files`) AS `images` FROM ``posts_%s`` WHERE `thread` = :thread", $board['uri'], $board['uri']));
$query->bindValue(':thread', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
@@ -1907,7 +1889,7 @@ function markup(&$body, $track_cites = false) {
}
// replace tabs with 8 spaces
- $body = str_replace("\t", ' ', $body);
+ $body = str_replace("\t", ' ', $body);
return $tracked_cites;
}
@@ -2054,8 +2036,8 @@ function buildThread50($id, $return = false, $mod = false, $thread = null, $anti
if (!isset($thread)) {
$thread = new Thread($post, $mod ? '?/' : $config['root'], $mod);
} else {
- if ($post['file'])
- $num_images++;
+ if ($post['files'])
+ $num_images += $post['num_files'];
$thread->add(new Post($post, $mod ? '?/' : $config['root'], $mod));
}
@@ -2067,7 +2049,8 @@ function buildThread50($id, $return = false, $mod = false, $thread = null, $anti
if ($query->rowCount() == $config['noko50_count']+1) {
- $count = 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", $board['uri'], $board['uri']));
+ $count = prepare(sprintf("SELECT COUNT(`id`) as `num` FROM ``posts_%s`` WHERE `thread` = :thread UNION ALL
+ SELECT SUM(`num_files`) FROM ``posts_%s`` WHERE `files` IS NOT NULL AND `thread` = :thread", $board['uri'], $board['uri']));
$count->bindValue(':thread', $id, PDO::PARAM_INT);
$count->execute() or error(db_error($count));
@@ -2087,8 +2070,8 @@ function buildThread50($id, $return = false, $mod = false, $thread = null, $anti
foreach ($allPosts as $index => $post) {
if ($index == count($allPosts)-count($thread->posts))
break;
- if ($post->file)
- $thread->omitted_images++;
+ if ($post->files)
+ $thread->omitted_images += $post->num_files;
}
}
@@ -2234,13 +2217,15 @@ function getPostByHashInThread($hash, $thread) {
}
function undoImage(array $post) {
- if (!$post['has_file'])
+ if (!$post['has_file'] || !isset($post['files']))
return;
- if (isset($post['file_path']))
- file_unlink($post['file_path']);
- if (isset($post['thumb_path']))
- file_unlink($post['thumb_path']);
+ foreach ($post['files'] as $key => $file) {
+ if (isset($file['file_path']))
+ file_unlink($file['file_path']);
+ if (isset($file['thumb_path']))
+ file_unlink($file['thumb_path']);
+ }
}
function rDNS($ip_addr) {
diff --git a/inc/lib/Twig/Extensions/Extension/Tinyboard.php b/inc/lib/Twig/Extensions/Extension/Tinyboard.php
index 727d5d2c..986ecf45 100644
--- a/inc/lib/Twig/Extensions/Extension/Tinyboard.php
+++ b/inc/lib/Twig/Extensions/Extension/Tinyboard.php
@@ -13,6 +13,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
new Twig_SimpleFilter('filesize', 'format_bytes'),
new Twig_SimpleFilter('truncate', 'twig_truncate_filter'),
new Twig_SimpleFilter('truncate_body', 'truncate'),
+ new Twig_SimpleFilter('truncate_filename', 'twig_filename_truncate_filter'),
new Twig_SimpleFilter('extension', 'twig_extension_filter'),
new Twig_SimpleFilter('sprintf', 'sprintf'),
new Twig_SimpleFilter('capcode', 'capcode'),
@@ -25,7 +26,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
new Twig_SimpleFilter('until', 'until'),
new Twig_SimpleFilter('push', 'twig_push_filter'),
new Twig_SimpleFilter('bidi_cleanup', 'bidi_cleanup'),
- new Twig_SimpleFilter('addslashes', 'addslashes')
+ new Twig_SimpleFilter('addslashes', 'addslashes'),
);
}
@@ -42,6 +43,9 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
new Twig_SimpleFunction('timezone', 'twig_timezone_function'),
new Twig_SimpleFunction('hiddenInputs', 'hiddenInputs'),
new Twig_SimpleFunction('hiddenInputsHash', 'hiddenInputsHash'),
+ new Twig_SimpleFunction('ratio', 'twig_ratio_function'),
+ new Twig_SimpleFunction('secure_link_confirm', 'twig_secure_link_confirm'),
+ new Twig_SimpleFunction('secure_link', 'twig_secure_link')
);
}
@@ -100,3 +104,30 @@ function twig_truncate_filter($value, $length = 30, $preserve = false, $separato
return $value;
}
+function twig_filename_truncate_filter($value, $length = 30, $separator = '&hellip;') {
+ if (mb_strlen($value) > $length) {
+ $value = strrev($value);
+ $array = array_reverse(explode(".", $value, 2));
+ $array = array_map("strrev", $array);
+
+ $filename = &$array[0];
+ $extension = isset($array[1]) ? $array[1] : false;
+
+ $filename = mb_substr($filename, 0, $length - ($extension ? mb_strlen($extension) + 1 : 0)) . $separator;
+
+ return implode(".", $array);
+ }
+ return $value;
+}
+
+function twig_ratio_function($w, $h) {
+ return fraction($w, $h, ':');
+}
+function twig_secure_link_confirm($text, $title, $confirm_message, $href) {
+ global $config;
+
+ return '<a onclick="if (event.which==2) return true;if (confirm(\'' . htmlentities(addslashes($confirm_message)) . '\')) document.location=\'?/' . htmlspecialchars(addslashes($href . '/' . make_secure_link_token($href))) . '\';return false;" title="' . htmlentities($title) . '" href="?/' . $href . '">' . $text . '</a>';
+}
+function twig_secure_link($href) {
+ return $href . '/' . make_secure_link_token($href);
+}
diff --git a/inc/lib/minify/JSMin.php b/inc/lib/minify/JSMin.php
index b6879f37..9840d8b3 100755
--- a/inc/lib/minify/JSMin.php
+++ b/inc/lib/minify/JSMin.php
@@ -7,14 +7,14 @@
* </code>
*
* This is a modified port of jsmin.c. Improvements:
- *
+ *
* Does not choke on some regexp literals containing quote characters. E.g. /'/
- *
- * Spaces are preserved after some add/sub operators, so they are not mistakenly
+ *
+ * Spaces are preserved after some add/sub operators, so they are not mistakenly
* converted to post-inc/dec. E.g. a + ++b -> a+ ++b
*
* Preserves multi-line comments that begin with /*!
- *
+ *
* PHP 5 or higher is required.
*
* Permission is hereby granted to use this version of the library under the
@@ -69,6 +69,7 @@ class JSMin {
protected $lookAhead = null;
protected $output = '';
protected $lastByteOut = '';
+ protected $keptComment = '';
/**
* Minify Javascript.
@@ -116,8 +117,8 @@ class JSMin {
// determine next command
$command = self::ACTION_KEEP_A; // default
if ($this->a === ' ') {
- if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
- && ($this->b === $this->lastByteOut)) {
+ if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
+ && ($this->b === $this->lastByteOut)) {
// Don't delete this space. If we do, the addition/subtraction
// could be parsed as a post-increment
} elseif (! $this->isAlphaNum($this->b)) {
@@ -126,16 +127,17 @@ class JSMin {
} elseif ($this->a === "\n") {
if ($this->b === ' ') {
$command = self::ACTION_DELETE_A_B;
- // in case of mbstring.func_overload & 2, must check for null b,
- // otherwise mb_strpos will give WARNING
+
+ // in case of mbstring.func_overload & 2, must check for null b,
+ // otherwise mb_strpos will give WARNING
} elseif ($this->b === null
- || (false === strpos('{[(+-', $this->b)
+ || (false === strpos('{[(+-!~', $this->b)
&& ! $this->isAlphaNum($this->b))) {
$command = self::ACTION_DELETE_A;
}
} elseif (! $this->isAlphaNum($this->a)) {
if ($this->b === ' '
- || ($this->b === "\n"
+ || ($this->b === "\n"
&& (false === strpos('}])+-"\'', $this->a)))) {
$command = self::ACTION_DELETE_A_B;
}
@@ -160,7 +162,8 @@ class JSMin {
*/
protected function action($command)
{
- if ($command === self::ACTION_DELETE_A_B
+ // make sure we don't compress "a + ++b" to "a+++b", etc.
+ if ($command === self::ACTION_DELETE_A_B
&& $this->b === ' '
&& ($this->a === '+' || $this->a === '-')) {
// Note: we're at an addition/substraction operator; the inputIndex
@@ -170,58 +173,88 @@ class JSMin {
$command = self::ACTION_KEEP_A;
}
}
+
switch ($command) {
- case self::ACTION_KEEP_A:
+ case self::ACTION_KEEP_A: // 1
$this->output .= $this->a;
+
+ if ($this->keptComment) {
+ $this->output = rtrim($this->output, "\n");
+ $this->output .= $this->keptComment;
+ $this->keptComment = '';
+ }
+
$this->lastByteOut = $this->a;
-
- // fallthrough
- case self::ACTION_DELETE_A:
+
+ // fallthrough intentional
+ case self::ACTION_DELETE_A: // 2
$this->a = $this->b;
if ($this->a === "'" || $this->a === '"') { // string literal
$str = $this->a; // in case needed for exception
- while (true) {
+ for(;;) {
$this->output .= $this->a;
$this->lastByteOut = $this->a;
-
- $this->a = $this->get();
+
+ $this->a = $this->get();
if ($this->a === $this->b) { // end quote
break;
}
- if (ord($this->a) <= self::ORD_LF) {
+ if ($this->isEOF($this->a)) {
+ $byte = $this->inputIndex - 1;
throw new JSMin_UnterminatedStringException(
- "JSMin: Unterminated String at byte "
- . $this->inputIndex . ": {$str}");
+ "JSMin: Unterminated String at byte {$byte}: {$str}");
}
$str .= $this->a;
if ($this->a === '\\') {
$this->output .= $this->a;
$this->lastByteOut = $this->a;
-
+
$this->a = $this->get();
$str .= $this->a;
}
}
}
- // fallthrough
- case self::ACTION_DELETE_A_B:
+
+ // fallthrough intentional
+ case self::ACTION_DELETE_A_B: // 3
$this->b = $this->next();
- if ($this->b === '/' && $this->isRegexpLiteral()) { // RegExp literal
+ if ($this->b === '/' && $this->isRegexpLiteral()) {
$this->output .= $this->a . $this->b;
- $pattern = '/'; // in case needed for exception
- while (true) {
+ $pattern = '/'; // keep entire pattern in case we need to report it in the exception
+ for(;;) {
$this->a = $this->get();
$pattern .= $this->a;
+ if ($this->a === '[') {
+ for(;;) {
+ $this->output .= $this->a;
+ $this->a = $this->get();
+ $pattern .= $this->a;
+ if ($this->a === ']') {
+ break;
+ }
+ if ($this->a === '\\') {
+ $this->output .= $this->a;
+ $this->a = $this->get();
+ $pattern .= $this->a;
+ }
+ if ($this->isEOF($this->a)) {
+ throw new JSMin_UnterminatedRegExpException(
+ "JSMin: Unterminated set in RegExp at byte "
+ . $this->inputIndex .": {$pattern}");
+ }
+ }
+ }
+
if ($this->a === '/') { // end pattern
break; // while (true)
} elseif ($this->a === '\\') {
$this->output .= $this->a;
- $this->a = $this->get();
- $pattern .= $this->a;
- } elseif (ord($this->a) <= self::ORD_LF) {
+ $this->a = $this->get();
+ $pattern .= $this->a;
+ } elseif ($this->isEOF($this->a)) {
+ $byte = $this->inputIndex - 1;
throw new JSMin_UnterminatedRegExpException(
- "JSMin: Unterminated RegExp at byte "
- . $this->inputIndex .": {$pattern}");
+ "JSMin: Unterminated RegExp at byte {$byte}: {$pattern}");
}
$this->output .= $this->a;
$this->lastByteOut = $this->a;
@@ -237,31 +270,43 @@ class JSMin {
*/
protected function isRegexpLiteral()
{
- if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing
+ if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) {
+ // we obviously aren't dividing
return true;
}
- if (' ' === $this->a) {
- $length = strlen($this->output);
- if ($length < 2) { // weird edge case
- return true;
+
+ // we have to check for a preceding keyword, and we don't need to pattern
+ // match over the whole output.
+ $recentOutput = substr($this->output, -10);
+
+ // check if return/typeof directly precede a pattern without a space
+ foreach (array('return', 'typeof') as $keyword) {
+ if ($this->a !== substr($keyword, -1)) {
+ // certainly wasn't keyword
+ continue;
}
- // you can't divide a keyword
- if (preg_match('/(?:case|else|in|return|typeof)$/', $this->output, $m)) {
- if ($this->output === $m[0]) { // odd but could happen
- return true;
- }
- // make sure it's a keyword, not end of an identifier
- $charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1);
- if (! $this->isAlphaNum($charBeforeKeyword)) {
+ if (preg_match("~(^|[\\s\\S])" . substr($keyword, 0, -1) . "$~", $recentOutput, $m)) {
+ if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
return true;
}
}
}
+
+ // check all keywords
+ if ($this->a === ' ' || $this->a === "\n") {
+ if (preg_match('~(^|[\\s\\S])(?:case|else|in|return|typeof)$~', $recentOutput, $m)) {
+ if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
+ return true;
+ }
+ }
+ }
+
return false;
}
/**
- * Get next char. Convert ctrl char to space.
+ * Return the next character from stdin. Watch out for lookahead. If the character is a control character,
+ * translate it to a space or linefeed.
*
* @return string
*/
@@ -270,24 +315,36 @@ class JSMin {
$c = $this->lookAhead;
$this->lookAhead = null;
if ($c === null) {
+ // getc(stdin)
if ($this->inputIndex < $this->inputLength) {
$c = $this->input[$this->inputIndex];
$this->inputIndex += 1;
} else {
- return null;
+ $c = null;
}
}
- if ($c === "\r" || $c === "\n") {
- return "\n";
+ if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) {
+ return $c;
}
- if (ord($c) < self::ORD_SPACE) { // control char
- return ' ';
+ if ($c === "\r") {
+ return "\n";
}
- return $c;
+ return ' ';
+ }
+
+ /**
+ * Does $a indicate end of input?
+ *
+ * @param string $a
+ * @return bool
+ */
+ protected function isEOF($a)
+ {
+ return ord($a) <= self::ORD_LF;
}
/**
- * Get next char. If is ctrl character, translate to a space or newline.
+ * Get next char (without getting it). If is ctrl character, translate to a space or newline.
*
* @return string
*/
@@ -298,7 +355,7 @@ class JSMin {
}
/**
- * Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII?
+ * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
*
* @param string $c
*
@@ -306,77 +363,84 @@ class JSMin {
*/
protected function isAlphaNum($c)
{
- return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126);
+ return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126);
}
/**
- * @return string
+ * Consume a single line comment from input (possibly retaining it)
*/
- protected function singleLineComment()
+ protected function consumeSingleLineComment()
{
$comment = '';
while (true) {
$get = $this->get();
$comment .= $get;
- if (ord($get) <= self::ORD_LF) { // EOL reached
+ if (ord($get) <= self::ORD_LF) { // end of line reached
// if IE conditional comment
if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
- return "/{$comment}";
+ $this->keptComment .= "/{$comment}";
}
- return $get;
+ return;
}
}
}
/**
- * @return string
+ * Consume a multiple line comment from input (possibly retaining it)
+ *
* @throws JSMin_UnterminatedCommentException
*/
- protected function multipleLineComment()
+ protected function consumeMultipleLineComment()
{
$this->get();
$comment = '';
- while (true) {
+ for(;;) {
$get = $this->get();
if ($get === '*') {
if ($this->peek() === '/') { // end of comment reached
$this->get();
- // if comment preserved by YUI Compressor
if (0 === strpos($comment, '!')) {
- return "\n/*!" . substr($comment, 1) . "*/\n";
- }
- // if IE conditional comment
- if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
- return "/*{$comment}*/";
+ // preserved by YUI Compressor
+ if (!$this->keptComment) {
+ // don't prepend a newline if two comments right after one another
+ $this->keptComment = "\n";
+ }
+ $this->keptComment .= "/*!" . substr($comment, 1) . "*/\n";
+ } else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
+ // IE conditional
+ $this->keptComment .= "/*{$comment}*/";
}
- return ' ';
+ return;
}
} elseif ($get === null) {
throw new JSMin_UnterminatedCommentException(
- "JSMin: Unterminated comment at byte "
- . $this->inputIndex . ": /*{$comment}");
+ "JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}");
}
$comment .= $get;
}
}
/**
- * Get the next character, skipping over comments.
- * Some comments may be preserved.
+ * Get the next character, skipping over comments. Some comments may be preserved.
*
* @return string
*/
protected function next()
{
$get = $this->get();
- if ($get !== '/') {
- return $get;
- }
- switch ($this->peek()) {
- case '/': return $this->singleLineComment();
- case '*': return $this->multipleLineComment();
- default: return $get;
+ if ($get === '/') {
+ switch ($this->peek()) {
+ case '/':
+ $this->consumeSingleLineComment();
+ $get = "\n";
+ break;
+ case '*':
+ $this->consumeMultipleLineComment();
+ $get = ' ';
+ break;
+ }
}
+ return $get;
}
}
diff --git a/inc/lib/minify/Minify.php b/inc/lib/minify/Minify.php
index 9634f221..2b38f89b 100755
--- a/inc/lib/minify/Minify.php
+++ b/inc/lib/minify/Minify.php
@@ -3,11 +3,6 @@
* Class Minify
* @package Minify
*/
-
-/**
- * Minify_Source
- */
-require_once 'Minify/Source.php';
/**
* Minify - Combines, minifies, and caches JavaScript and CSS files on demand.
@@ -29,7 +24,7 @@ require_once 'Minify/Source.php';
*/
class Minify {
- const VERSION = '2.1.5';
+ const VERSION = '2.2.0';
const TYPE_CSS = 'text/css';
const TYPE_HTML = 'text/html';
// there is some debate over the ideal JS Content-Type, but this is the
@@ -85,7 +80,6 @@ class Minify {
public static function setCache($cache = '', $fileLocking = true)
{
if (is_string($cache)) {
- require_once 'Minify/Cache/File.php';
self::$_cache = new Minify_Cache_File($cache, $fileLocking);
} else {
self::$_cache = $cache;
@@ -161,9 +155,11 @@ class Minify {
*
* @param array $options controller/serve options
*
- * @return mixed null, or, if the 'quiet' option is set to true, an array
+ * @return null|array if the 'quiet' option is set to true, an array
* with keys "success" (bool), "statusCode" (int), "content" (string), and
* "headers" (array).
+ *
+ * @throws Exception
*/
public static function serve($controller, $options = array())
{
@@ -174,10 +170,6 @@ class Minify {
if (is_string($controller)) {
// make $controller into object
$class = 'Minify_Controller_' . $controller;
- if (! class_exists($class, false)) {
- require_once "Minify/Controller/"
- . str_replace('_', '/', $controller) . ".php";
- }
$controller = new $class();
/* @var Minify_Controller_Base $controller */
}
@@ -219,8 +211,7 @@ class Minify {
$contentEncoding = self::$_options['encodeMethod'];
} else {
// sniff request header
- require_once 'HTTP/Encoder.php';
- // depending on what the client accepts, $contentEncoding may be
+ // depending on what the client accepts, $contentEncoding may be
// 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
// getAcceptedEncoding(false, false) leaves out compress and deflate as options.
list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false);
@@ -231,7 +222,6 @@ class Minify {
}
// check client cache
- require_once 'HTTP/ConditionalGet.php';
$cgOptions = array(
'lastModifiedTime' => self::$_options['lastModifiedTime']
,'isPublic' => self::$_options['isPublic']
@@ -300,7 +290,7 @@ class Minify {
throw $e;
}
self::$_cache->store($cacheId, $content);
- if (function_exists('gzencode')) {
+ if (function_exists('gzencode') && self::$_options['encodeMethod']) {
self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel']));
}
}
@@ -451,7 +441,7 @@ class Minify {
/**
* Set up sources to use Minify_Lines
*
- * @param array $sources Minify_Source instances
+ * @param Minify_Source[] $sources Minify_Source instances
*/
protected static function _setupDebug($sources)
{
@@ -468,6 +458,8 @@ class Minify {
* Combines sources and minifies the result.
*
* @return string
+ *
+ * @throws Exception
*/
protected static function _combineMinify()
{
@@ -526,7 +518,6 @@ class Minify {
$imploded = implode($implodeSeparator, $groupToProcessTogether);
$groupToProcessTogether = array();
if ($lastMinifier) {
- self::$_controller->loadMinifier($lastMinifier);
try {
$content[] = call_user_func($lastMinifier, $imploded, $lastOptions);
} catch (Exception $e) {
@@ -574,7 +565,7 @@ class Minify {
{
$name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId);
$name = preg_replace('/\\.+/', '.', $name);
- $name = substr($name, 0, 200 - 34 - strlen($prefix));
+ $name = substr($name, 0, 100 - 34 - strlen($prefix));
$md5 = md5(serialize(array(
Minify_Source::getDigest(self::$_controller->sources)
,self::$_options['minifiers']
diff --git a/inc/lib/minify/Minify/Build.php b/inc/lib/minify/Minify/Build.php
index e625165e..1185fbe7 100755
--- a/inc/lib/minify/Minify/Build.php
+++ b/inc/lib/minify/Minify/Build.php
@@ -4,8 +4,6 @@
* @package Minify
*/
-require_once 'Minify/Source.php';
-
/**
* Maintain a single last modification time for a group of Minify sources to
* allow use of far off Expires headers in Minify.
diff --git a/inc/lib/minify/Minify/CSS.php b/inc/lib/minify/Minify/CSS.php
index 49882e9d..32414551 100755
--- a/inc/lib/minify/Minify/CSS.php
+++ b/inc/lib/minify/Minify/CSS.php
@@ -56,6 +56,7 @@ class Minify_CSS {
public static function minify($css, $options = array())
{
$options = array_merge(array(
+ 'compress' => true,
'removeCharsets' => true,
'preserveComments' => true,
'currentDir' => null,
@@ -67,21 +68,20 @@ class Minify_CSS {
if ($options['removeCharsets']) {
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
}
- require_once 'Minify/CSS/Compressor.php';
- if (! $options['preserveComments']) {
- $css = Minify_CSS_Compressor::process($css, $options);
- } else {
- require_once 'Minify/CommentPreserver.php';
- $css = Minify_CommentPreserver::process(
- $css
- ,array('Minify_CSS_Compressor', 'process')
- ,array($options)
- );
+ if ($options['compress']) {
+ if (! $options['preserveComments']) {
+ $css = Minify_CSS_Compressor::process($css, $options);
+ } else {
+ $css = Minify_CommentPreserver::process(
+ $css
+ ,array('Minify_CSS_Compressor', 'process')
+ ,array($options)
+ );
+ }
}
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
return $css;
}
- require_once 'Minify/CSS/UriRewriter.php';
if ($options['currentDir']) {
return Minify_CSS_UriRewriter::rewrite(
$css
diff --git a/inc/lib/minify/Minify/CSS/UriRewriter.php b/inc/lib/minify/Minify/CSS/UriRewriter.php
index 8845f156..43cc2548 100755
--- a/inc/lib/minify/Minify/CSS/UriRewriter.php
+++ b/inc/lib/minify/Minify/CSS/UriRewriter.php
@@ -70,7 +70,7 @@ class Minify_CSS_UriRewriter {
// rewrite
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
,array(self::$className, '_processUriCB'), $css);
- $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+ $css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
,array(self::$className, '_processUriCB'), $css);
return $css;
@@ -94,7 +94,7 @@ class Minify_CSS_UriRewriter {
// append
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
,array(self::$className, '_processUriCB'), $css);
- $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+ $css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
,array(self::$className, '_processUriCB'), $css);
self::$_prependPath = null;
@@ -282,11 +282,8 @@ class Minify_CSS_UriRewriter {
? $m[1]
: substr($m[1], 1, strlen($m[1]) - 2);
}
- // analyze URI
- if ('/' !== $uri[0] // root-relative
- && false === strpos($uri, '//') // protocol (non-data)
- && 0 !== strpos($uri, 'data:') // data protocol
- ) {
+ // if not root/scheme relative and not starts with scheme
+ if (!preg_match('~^(/|[a-z]+\:)~', $uri)) {
// URI is file-relative: rewrite depending on options
if (self::$_prependPath === null) {
$uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
diff --git a/inc/lib/minify/Minify/Cache/File.php b/inc/lib/minify/Minify/Cache/File.php
index 3fa6b18f..c228eb2b 100755
--- a/inc/lib/minify/Minify/Cache/File.php
+++ b/inc/lib/minify/Minify/Cache/File.php
@@ -98,6 +98,9 @@ class Minify_Cache_File {
{
if ($this->_locking) {
$fp = fopen($this->_path . '/' . $id, 'rb');
+ if (!$fp) {
+ return false;
+ }
flock($fp, LOCK_SH);
$ret = stream_get_contents($fp);
flock($fp, LOCK_UN);
@@ -186,7 +189,6 @@ class Minify_Cache_File {
*/
protected function _log($msg)
{
- require_once 'Minify/Logger.php';
Minify_Logger::log($msg);
}
diff --git a/inc/lib/minify/Minify/Controller/Base.php b/inc/lib/minify/Minify/Controller/Base.php
index 240b5449..5a863290 100755
--- a/inc/lib/minify/Minify/Controller/Base.php
+++ b/inc/lib/minify/Minify/Controller/Base.php
@@ -79,33 +79,6 @@ abstract class Minify_Controller_Base {
}
/**
- * Load any code necessary to execute the given minifier callback.
- *
- * The controller is responsible for loading minification code on demand
- * via this method. This built-in function will only load classes for
- * static method callbacks where the class isn't already defined. It uses
- * the PEAR convention, so, given array('Jimmy_Minifier', 'minCss'), this
- * function will include 'Jimmy/Minifier.php'.
- *
- * If you need code loaded on demand and this doesn't suit you, you'll need
- * to override this function in your subclass.
- * @see Minify_Controller_Page::loadMinifier()
- *
- * @param callback $minifierCallback callback of minifier function
- *
- * @return null
- */
- public function loadMinifier($minifierCallback)
- {
- if (is_array($minifierCallback)
- && is_string($minifierCallback[0])
- && !class_exists($minifierCallback[0], false)) {
-
- require str_replace('_', '/', $minifierCallback[0]) . '.php';
- }
- }
-
- /**
* Is a user-given file within an allowable directory, existing,
* and having an extension js/css/html/txt ?
*
@@ -244,7 +217,6 @@ abstract class Minify_Controller_Base {
* @return null
*/
public function log($msg) {
- require_once 'Minify/Logger.php';
Minify_Logger::log($msg);
}
}
diff --git a/inc/lib/minify/Minify/Controller/Files.php b/inc/lib/minify/Minify/Controller/Files.php
index 83f028ad..f084cd07 100755
--- a/inc/lib/minify/Minify/Controller/Files.php
+++ b/inc/lib/minify/Minify/Controller/Files.php
@@ -4,8 +4,6 @@
* @package Minify
*/
-require_once 'Minify/Controller/Base.php';
-
/**
* Controller class for minifying a set of files
*
diff --git a/inc/lib/minify/Minify/Controller/Groups.php b/inc/lib/minify/Minify/Controller/Groups.php
index 2d4e43b2..c4c25db1 100755
--- a/inc/lib/minify/Minify/Controller/Groups.php
+++ b/inc/lib/minify/Minify/Controller/Groups.php
@@ -4,8 +4,6 @@
* @package Minify
*/
-require_once 'Minify/Controller/Base.php';
-
/**
* Controller class for serving predetermined groups of minimized sets, selected
* by PATH_INFO
diff --git a/inc/lib/minify/Minify/Controller/MinApp.php b/inc/lib/minify/Minify/Controller/MinApp.php
index d47c60d9..6943ee6b 100755
--- a/inc/lib/minify/Minify/Controller/MinApp.php
+++ b/inc/lib/minify/Minify/Controller/MinApp.php
@@ -4,8 +4,6 @@
* @package Minify
*/
-require_once 'Minify/Controller/Base.php';
-
/**
* Controller class for requests to /min/index.php
*
@@ -22,6 +20,13 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
* @return array Minify options
*/
public function setupSources($options) {
+ // PHP insecure by default: realpath() and other FS functions can't handle null bytes.
+ foreach (array('g', 'b', 'f') as $key) {
+ if (isset($_GET[$key])) {
+ $_GET[$key] = str_replace("\x00", '', (string)$_GET[$key]);
+ }
+ }
+
// filter controller options
$cOptions = array_merge(
array(
@@ -36,7 +41,6 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
$sources = array();
$this->selectionId = '';
$firstMissingResource = null;
-
if (isset($_GET['g'])) {
// add group(s)
$this->selectionId .= 'g=' . $_GET['g'];
@@ -195,9 +199,12 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
protected function _getFileSource($file, $cOptions)
{
$spec['filepath'] = $file;
- if ($cOptions['noMinPattern']
- && preg_match($cOptions['noMinPattern'], basename($file))) {
- $spec['minifier'] = '';
+ if ($cOptions['noMinPattern'] && preg_match($cOptions['noMinPattern'], basename($file))) {
+ if (preg_match('~\.css$~i', $file)) {
+ $spec['minifyOptions']['compress'] = false;
+ } else {
+ $spec['minifier'] = '';
+ }
}
return new Minify_Source($spec);
}
diff --git a/inc/lib/minify/Minify/Controller/Page.php b/inc/lib/minify/Minify/Controller/Page.php
index de471e12..1095fb46 100755
--- a/inc/lib/minify/Minify/Controller/Page.php
+++ b/inc/lib/minify/Minify/Controller/Page.php
@@ -4,8 +4,6 @@
* @package Minify
*/
-require_once 'Minify/Controller/Base.php';
-
/**
* Controller class for serving a single HTML page
*
@@ -59,7 +57,6 @@ class Minify_Controller_Page extends Minify_Controller_Base {
'cssMinifier' => array('Minify_CSS', 'minify')
,'jsMinifier' => array('JSMin', 'minify')
);
- $this->_loadCssJsMinifiers = true;
unset($options['minifyAll']);
}
$this->sources[] = new Minify_Source($sourceSpec);
@@ -67,21 +64,5 @@ class Minify_Controller_Page extends Minify_Controller_Base {
$options['contentType'] = Minify::TYPE_HTML;
return $options;
}
-
- protected $_loadCssJsMinifiers = false;
-
- /**
- * @see Minify_Controller_Base::loadMinifier()
- */
- public function loadMinifier($minifierCallback)
- {
- if ($this->_loadCssJsMinifiers) {
- // Minify will not call for these so we must manually load
- // them when Minify/HTML.php is called for.
- require_once 'Minify/CSS.php';
- require_once 'JSMin.php';
- }
- parent::loadMinifier($minifierCallback); // load Minify/HTML.php
- }
}
diff --git a/inc/lib/minify/Minify/Controller/Version1.php b/inc/lib/minify/Minify/Controller/Version1.php
index 5279d36d..91fcf614 100755
--- a/inc/lib/minify/Minify/Controller/Version1.php
+++ b/inc/lib/minify/Minify/Controller/Version1.php
@@ -4,8 +4,6 @@
* @package Minify
*/
-require_once 'Minify/Controller/Base.php';
-
/**
* Controller class for emulating version 1 of minify.php (mostly a proof-of-concept)
*
@@ -26,6 +24,11 @@ class Minify_Controller_Version1 extends Minify_Controller_Base {
*
*/
public function setupSources($options) {
+ // PHP insecure by default: realpath() and other FS functions can't handle null bytes.
+ if (isset($_GET['files'])) {
+ $_GET['files'] = str_replace("\x00", '', (string)$_GET['files']);
+ }
+
self::_setupDefines();
if (MINIFY_USE_CACHE) {
$cacheDir = defined('MINIFY_CACHE_DIR')
@@ -51,8 +54,7 @@ class Minify_Controller_Version1 extends Minify_Controller_Base {
) {
return $options;
}
- $extension = $m[1];
-
+
$files = explode(',', $_GET['files']);
if (count($files) > MINIFY_MAX_FILES) {
return $options;
@@ -63,7 +65,6 @@ class Minify_Controller_Version1 extends Minify_Controller_Base {
. DIRECTORY_SEPARATOR;
$prependAbsPaths = $_SERVER['DOCUMENT_ROOT'];
- $sources = array();
$goodFiles = array();
$hasBadSource = false;
diff --git a/inc/lib/minify/Minify/HTML.php b/inc/lib/minify/Minify/HTML.php
index e9453ffb..40f73076 100755
--- a/inc/lib/minify/Minify/HTML.php
+++ b/inc/lib/minify/Minify/HTML.php
@@ -1,22 +1,26 @@
<?php
/**
- * Class Minify_HTML
+ * Class Minify_HTML
* @package Minify
*/
/**
* Compress HTML
*
- * This is a heavy regex-based removal of whitespace, unnecessary comments and
+ * This is a heavy regex-based removal of whitespace, unnecessary comments and
* tokens. IE conditional comments are preserved. There are also options to have
- * STYLE and SCRIPT blocks compressed by callback functions.
- *
+ * STYLE and SCRIPT blocks compressed by callback functions.
+ *
* A test suite is available.
- *
+ *
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*/
class Minify_HTML {
+ /**
+ * @var boolean
+ */
+ protected $_jsCleanComments = true;
/**
* "Minify" an HTML page
@@ -27,21 +31,21 @@ class Minify_HTML {
*
* 'cssMinifier' : (optional) callback function to process content of STYLE
* elements.
- *
+ *
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
* elements. Note: the type attribute is ignored.
- *
+ *
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
- *
+ *
* @return string
*/
public static function minify($html, $options = array()) {
- $min = new Minify_HTML($html, $options);
+ $min = new self($html, $options);
return $min->process();
}
-
-
+
+
/**
* Create a minifier object
*
@@ -51,14 +55,14 @@ class Minify_HTML {
*
* 'cssMinifier' : (optional) callback function to process content of STYLE
* elements.
- *
+ *
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
* elements. Note: the type attribute is ignored.
- *
+ *
+ * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
+ *
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
- *
- * @return null
*/
public function __construct($html, $options = array())
{
@@ -72,9 +76,12 @@ class Minify_HTML {
if (isset($options['jsMinifier'])) {
$this->_jsMinifier = $options['jsMinifier'];
}
+ if (isset($options['jsCleanComments'])) {
+ $this->_jsCleanComments = (bool)$options['jsCleanComments'];
+ }
}
-
-
+
+
/**
* Minify the markeup given in the constructor
*
@@ -124,7 +131,7 @@ class Minify_HTML {
// remove ws around block/undisplayed elements
$this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
- .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
+ .'|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
.'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
.'|ul)\\b[^>]*>)/i', '$1', $this->_html);
@@ -213,17 +220,19 @@ class Minify_HTML {
// whitespace surrounding? preserve at least one space
$ws1 = ($m[1] === '') ? '' : ' ';
$ws2 = ($m[4] === '') ? '' : ' ';
-
+
// remove HTML comments (and ending "//" if present)
- $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
-
+ if ($this->_jsCleanComments) {
+ $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
+ }
+
// remove CDATA section markers
$js = $this->_removeCdata($js);
// minify
$minifier = $this->_jsMinifier
? $this->_jsMinifier
- : 'trim';
+ : 'trim';
$js = call_user_func($minifier, $js);
return $this->_reservePlace($this->_needsCdata($js)
diff --git a/inc/lib/minify/Minify/HTML/Helper.php b/inc/lib/minify/Minify/HTML/Helper.php
index 807fdc9c..f92ab854 100755
--- a/inc/lib/minify/Minify/HTML/Helper.php
+++ b/inc/lib/minify/Minify/HTML/Helper.php
@@ -15,10 +15,10 @@ class Minify_HTML_Helper {
public $minAppUri = '/min';
public $groupsConfigFile = '';
- /*
+ /**
* Get an HTML-escaped Minify URI for a group or set of files
*
- * @param mixed $keyOrFiles a group key or array of filepaths/URIs
+ * @param string|array $keyOrFiles a group key or array of filepaths/URIs
* @param array $opts options:
* 'farExpires' : (default true) append a modified timestamp for cache revving
* 'debug' : (default false) append debug flag
@@ -51,8 +51,12 @@ class Minify_HTML_Helper {
return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']);
}
- /*
+ /**
* Get non-HTML-escaped URI to minify the specified files
+ *
+ * @param bool $farExpires
+ * @param bool $debug
+ * @return string
*/
public function getRawUri($farExpires = true, $debug = false)
{
@@ -74,6 +78,12 @@ class Minify_HTML_Helper {
return $path;
}
+ /**
+ * Set the files that will comprise the URI we're building
+ *
+ * @param array $files
+ * @param bool $checkLastModified
+ */
public function setFiles($files, $checkLastModified = true)
{
$this->_groupKey = null;
@@ -94,6 +104,12 @@ class Minify_HTML_Helper {
$this->_filePaths = $files;
}
+ /**
+ * Set the group of files that will comprise the URI we're building
+ *
+ * @param string $key
+ * @param bool $checkLastModified
+ */
public function setGroup($key, $checkLastModified = true)
{
$this->_groupKey = $key;
@@ -103,13 +119,23 @@ class Minify_HTML_Helper {
}
if (is_file($this->groupsConfigFile)) {
$gc = (require $this->groupsConfigFile);
- if (isset($gc[$key])) {
- $this->_lastModified = self::getLastModified($gc[$key]);
+ $keys = explode(',', $key);
+ foreach ($keys as $key) {
+ if (isset($gc[$key])) {
+ $this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified);
+ }
}
}
}
}
-
+
+ /**
+ * Get the max(lastModified) of all files
+ *
+ * @param array|string $sources
+ * @param int $lastModified
+ * @return int
+ */
public static function getLastModified($sources, $lastModified = 0)
{
$max = $lastModified;
@@ -142,13 +168,19 @@ class Minify_HTML_Helper {
* @return mixed a common char or '' if any do not match
*/
protected static function _getCommonCharAtPos($arr, $pos) {
- $l = count($arr);
+ if (!isset($arr[0][$pos])) {
+ return '';
+ }
$c = $arr[0][$pos];
- if ($c === '' || $l === 1)
+ $l = count($arr);
+ if ($l === 1) {
return $c;
- for ($i = 1; $i < $l; ++$i)
- if ($arr[$i][$pos] !== $c)
+ }
+ for ($i = 1; $i < $l; ++$i) {
+ if ($arr[$i][$pos] !== $c) {
return '';
+ }
+ }
return $c;
}
@@ -157,11 +189,11 @@ class Minify_HTML_Helper {
*
* @param array $paths root-relative URIs of files
* @param string $minRoot root-relative URI of the "min" application
+ * @return string
*/
protected static function _getShortestUri($paths, $minRoot = '/min/') {
$pos = 0;
$base = '';
- $c;
while (true) {
$c = self::_getCommonCharAtPos($paths, $pos);
if ($c === '') {
diff --git a/inc/lib/minify/Minify/JS/ClosureCompiler.php b/inc/lib/minify/Minify/JS/ClosureCompiler.php
index f4ed5bdb..e067d7c8 100755
--- a/inc/lib/minify/Minify/JS/ClosureCompiler.php
+++ b/inc/lib/minify/Minify/JS/ClosureCompiler.php
@@ -14,13 +14,68 @@
* @todo can use a stream wrapper to unit test this?
*/
class Minify_JS_ClosureCompiler {
- const URL = 'http://closure-compiler.appspot.com/compile';
/**
- * Minify Javascript code via HTTP request to the Closure Compiler API
+ * @var string The option key for the maximum POST byte size
+ */
+ const OPTION_MAX_BYTES = 'maxBytes';
+
+ /**
+ * @var string The option key for additional params. @see __construct
+ */
+ const OPTION_ADDITIONAL_OPTIONS = 'additionalParams';
+
+ /**
+ * @var string The option key for the fallback Minifier
+ */
+ const OPTION_FALLBACK_FUNCTION = 'fallbackFunc';
+
+ /**
+ * @var string The option key for the service URL
+ */
+ const OPTION_COMPILER_URL = 'compilerUrl';
+
+ /**
+ * @var int The default maximum POST byte size according to https://developers.google.com/closure/compiler/docs/api-ref
+ */
+ const DEFAULT_MAX_BYTES = 200000;
+
+ /**
+ * @var string[] $DEFAULT_OPTIONS The default options to pass to the compiler service
+ *
+ * @note This would be a constant if PHP allowed it
+ */
+ private static $DEFAULT_OPTIONS = array(
+ 'output_format' => 'text',
+ 'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
+ );
+
+ /**
+ * @var string $url URL of compiler server. defaults to Google's
+ */
+ protected $serviceUrl = 'http://closure-compiler.appspot.com/compile';
+
+ /**
+ * @var int $maxBytes The maximum JS size that can be sent to the compiler server in bytes
+ */
+ protected $maxBytes = self::DEFAULT_MAX_BYTES;
+
+ /**
+ * @var string[] $additionalOptions Additional options to pass to the compiler service
+ */
+ protected $additionalOptions = array();
+
+ /**
+ * @var callable Function to minify JS if service fails. Default is JSMin
+ */
+ protected $fallbackMinifier = array('JSMin', 'minify');
+
+ /**
+ * Minify JavaScript code via HTTP request to a Closure Compiler API
*
* @param string $js input code
- * @param array $options unused at this point
+ * @param array $options Options passed to __construct(). @see __construct
+ *
* @return string
*/
public static function minify($js, array $options = array())
@@ -30,63 +85,101 @@ class Minify_JS_ClosureCompiler {
}
/**
+ * @param array $options Options with keys available below:
+ *
+ * fallbackFunc : (callable) function to minify if service unavailable. Default is JSMin.
*
- * @param array $options
+ * compilerUrl : (string) URL to closure compiler server
*
- * fallbackFunc : default array($this, 'fallback');
+ * maxBytes : (int) The maximum amount of bytes to be sent as js_code in the POST request.
+ * Defaults to 200000.
+ *
+ * additionalParams : (string[]) Additional parameters to pass to the compiler server. Can be anything named
+ * in https://developers.google.com/closure/compiler/docs/api-ref except for js_code and
+ * output_info
*/
public function __construct(array $options = array())
{
- $this->_fallbackFunc = isset($options['fallbackMinifier'])
- ? $options['fallbackMinifier']
- : array($this, '_fallback');
+ if (isset($options[self::OPTION_FALLBACK_FUNCTION])) {
+ $this->fallbackMinifier = $options[self::OPTION_FALLBACK_FUNCTION];
+ }
+ if (isset($options[self::OPTION_COMPILER_URL])) {
+ $this->serviceUrl = $options[self::OPTION_COMPILER_URL];
+ }
+ if (isset($options[self::OPTION_ADDITIONAL_OPTIONS]) && is_array($options[self::OPTION_ADDITIONAL_OPTIONS])) {
+ $this->additionalOptions = $options[self::OPTION_ADDITIONAL_OPTIONS];
+ }
+ if (isset($options[self::OPTION_MAX_BYTES])) {
+ $this->maxBytes = (int) $options[self::OPTION_MAX_BYTES];
+ }
}
+ /**
+ * Call the service to perform the minification
+ *
+ * @param string $js JavaScript code
+ * @return string
+ * @throws Minify_JS_ClosureCompiler_Exception
+ */
public function min($js)
{
- $postBody = $this->_buildPostBody($js);
- $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
- ? mb_strlen($postBody, '8bit')
- : strlen($postBody);
- if ($bytes > 200000) {
- throw new Minify_JS_ClosureCompiler_Exception(
- 'POST content larger than 200000 bytes'
- );
+ $postBody = $this->buildPostBody($js);
+
+ if ($this->maxBytes > 0) {
+ $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
+ ? mb_strlen($postBody, '8bit')
+ : strlen($postBody);
+ if ($bytes > $this->maxBytes) {
+ throw new Minify_JS_ClosureCompiler_Exception(
+ 'POST content larger than ' . $this->maxBytes . ' bytes'
+ );
+ }
}
- $response = $this->_getResponse($postBody);
+
+ $response = $this->getResponse($postBody);
+
if (preg_match('/^Error\(\d\d?\):/', $response)) {
- if (is_callable($this->_fallbackFunc)) {
+ if (is_callable($this->fallbackMinifier)) {
+ // use fallback
$response = "/* Received errors from Closure Compiler API:\n$response"
. "\n(Using fallback minifier)\n*/\n";
- $response .= call_user_func($this->_fallbackFunc, $js);
+ $response .= call_user_func($this->fallbackMinifier, $js);
} else {
throw new Minify_JS_ClosureCompiler_Exception($response);
}
}
+
if ($response === '') {
- $errors = $this->_getResponse($this->_buildPostBody($js, true));
+ $errors = $this->getResponse($this->buildPostBody($js, true));
throw new Minify_JS_ClosureCompiler_Exception($errors);
}
+
return $response;
}
-
- protected $_fallbackFunc = null;
- protected function _getResponse($postBody)
+ /**
+ * Get the response for a given POST body
+ *
+ * @param string $postBody
+ * @return string
+ * @throws Minify_JS_ClosureCompiler_Exception
+ */
+ protected function getResponse($postBody)
{
$allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
+
if ($allowUrlFopen) {
- $contents = file_get_contents(self::URL, false, stream_context_create(array(
+ $contents = file_get_contents($this->serviceUrl, false, stream_context_create(array(
'http' => array(
'method' => 'POST',
- 'header' => 'Content-type: application/x-www-form-urlencoded',
+ 'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n",
'content' => $postBody,
'max_redirects' => 0,
'timeout' => 15,
)
)));
} elseif (defined('CURLOPT_POST')) {
- $ch = curl_init(self::URL);
+ $ch = curl_init($this->serviceUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
@@ -100,33 +193,37 @@ class Minify_JS_ClosureCompiler {
"Could not make HTTP request: allow_url_open is false and cURL not available"
);
}
+
if (false === $contents) {
throw new Minify_JS_ClosureCompiler_Exception(
"No HTTP response from server"
);
}
- return trim($contents);
- }
- protected function _buildPostBody($js, $returnErrors = false)
- {
- return http_build_query(array(
- 'js_code' => $js,
- 'output_info' => ($returnErrors ? 'errors' : 'compiled_code'),
- 'output_format' => 'text',
- 'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
- ), null, '&');
+ return trim($contents);
}
/**
- * Default fallback function if CC API fails
- * @param string $js
+ * Build a POST request body
+ *
+ * @param string $js JavaScript code
+ * @param bool $returnErrors
* @return string
*/
- protected function _fallback($js)
+ protected function buildPostBody($js, $returnErrors = false)
{
- require_once 'JSMin.php';
- return JSMin::minify($js);
+ return http_build_query(
+ array_merge(
+ self::$DEFAULT_OPTIONS,
+ $this->additionalOptions,
+ array(
+ 'js_code' => $js,
+ 'output_info' => ($returnErrors ? 'errors' : 'compiled_code')
+ )
+ ),
+ null,
+ '&'
+ );
}
}
diff --git a/inc/lib/minify/Minify/Lines.php b/inc/lib/minify/Minify/Lines.php
index ca8afa1b..e999a6c2 100755
--- a/inc/lib/minify/Minify/Lines.php
+++ b/inc/lib/minify/Minify/Lines.php
@@ -55,7 +55,11 @@ class Minify_Lines {
$newLines = array();
while (null !== ($line = array_shift($lines))) {
if (('' !== $id) && (0 == $i % 50)) {
- array_push($newLines, '', "/* {$id} */", '');
+ if ($inComment) {
+ array_push($newLines, '', "/* {$id} *|", '');
+ } else {
+ array_push($newLines, '', "/* {$id} */", '');
+ }
}
++$i;
$newLines[] = self::_addNote($line, $i, $inComment, $padTo);
@@ -65,7 +69,6 @@ class Minify_Lines {
// check for desired URI rewriting
if (isset($options['currentDir'])) {
- require_once 'Minify/CSS/UriRewriter.php';
Minify_CSS_UriRewriter::$debugText = '';
$content = Minify_CSS_UriRewriter::rewrite(
$content
@@ -93,6 +96,9 @@ class Minify_Lines {
*/
private static function _eolInComment($line, $inComment)
{
+ // crude way to avoid things like // */
+ $line = preg_replace('~//.*?(\\*/|/\\*).*~', '', $line);
+
while (strlen($line)) {
$search = $inComment
? '*/'
diff --git a/inc/lib/minify/Minify/YUICompressor.php b/inc/lib/minify/Minify/YUICompressor.php
index c5bd8a1e..5762e890 100755
--- a/inc/lib/minify/Minify/YUICompressor.php
+++ b/inc/lib/minify/Minify/YUICompressor.php
@@ -13,14 +13,17 @@
* Java environment.
*
* <code>
- * Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.3.5.jar';
+ * Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.4.6.jar';
* Minify_YUICompressor::$tempDir = '/tmp';
* $code = Minify_YUICompressor::minifyJs(
* $code
* ,array('nomunge' => true, 'line-break' => 1000)
* );
* </code>
- *
+ *
+ * Note: In case you run out stack (default is 512k), you may increase stack size in $options:
+ * array('stack-size' => '2048k')
+ *
* @todo unit tests, $options docs
*
* @package Minify
@@ -87,7 +90,7 @@ class Minify_YUICompressor {
{
self::_prepare();
if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) {
- throw new Exception('Minify_YUICompressor : could not create temp file.');
+ throw new Exception('Minify_YUICompressor : could not create temp file in "'.self::$tempDir.'".');
}
file_put_contents($tmpFile, $content);
exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code);
@@ -108,10 +111,15 @@ class Minify_YUICompressor {
,'nomunge' => false
,'preserve-semi' => false
,'disable-optimizations' => false
+ ,'stack-size' => ''
)
,$userOptions
);
- $cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile)
+ $cmd = self::$javaExecutable
+ . (!empty($o['stack-size'])
+ ? ' -Xss' . $o['stack-size']
+ : '')
+ . ' -jar ' . escapeshellarg(self::$jarFile)
. " --type {$type}"
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
? " --charset {$o['charset']}"
@@ -134,8 +142,8 @@ class Minify_YUICompressor {
if (! is_file(self::$jarFile)) {
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
}
- if (! is_executable(self::$jarFile)) {
- throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not executable.');
+ if (! is_readable(self::$jarFile)) {
+ throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not readable.');
}
if (! is_dir(self::$tempDir)) {
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.');
diff --git a/inc/lib/minify/MrClay/Cli.php b/inc/lib/minify/MrClay/Cli.php
index ac201582..9aa8e06f 100755
--- a/inc/lib/minify/MrClay/Cli.php
+++ b/inc/lib/minify/MrClay/Cli.php
@@ -2,6 +2,9 @@
namespace MrClay;
+use MrClay\Cli\Arg;
+use InvalidArgumentException;
+
/**
* Forms a front controller for a console app, handling and validating arguments (options)
*
@@ -51,7 +54,7 @@ class Cli {
public $isHelpRequest = false;
/**
- * @var array of Cli\Arg
+ * @var Arg[]
*/
protected $_args = array();
@@ -80,8 +83,8 @@ class Cli {
}
/**
- * @param Cli\Arg|string $letter
- * @return Cli\Arg
+ * @param Arg|string $letter
+ * @return Arg
*/
public function addOptionalArg($letter)
{
@@ -89,8 +92,8 @@ class Cli {
}
/**
- * @param Cli\Arg|string $letter
- * @return Cli\Arg
+ * @param Arg|string $letter
+ * @return Arg
*/
public function addRequiredArg($letter)
{
@@ -100,17 +103,17 @@ class Cli {
/**
* @param string $letter
* @param bool $required
- * @param Cli\Arg|null $arg
- * @return Cli\Arg
- * @throws \InvalidArgumentException
+ * @param Arg|null $arg
+ * @return Arg
+ * @throws InvalidArgumentException
*/
- public function addArgument($letter, $required, Cli\Arg $arg = null)
+ public function addArgument($letter, $required, Arg $arg = null)
{
if (! preg_match('/^[a-zA-Z]$/', $letter)) {
- throw new \InvalidArgumentException('$letter must be in [a-zA-z]');
+ throw new InvalidArgumentException('$letter must be in [a-zA-Z]');
}
if (! $arg) {
- $arg = new Cli\Arg($required);
+ $arg = new Arg($required);
}
$this->_args[$letter] = $arg;
return $arg;
@@ -118,7 +121,7 @@ class Cli {
/**
* @param string $letter
- * @return Cli\Arg|null
+ * @return Arg|null
*/
public function getArgument($letter)
{
@@ -143,7 +146,7 @@ class Cli {
$lettersUsed = '';
foreach ($this->_args as $letter => $arg) {
- /* @var Cli\Arg $arg */
+ /* @var Arg $arg */
$options .= $letter;
$lettersUsed .= $letter;
@@ -159,7 +162,7 @@ class Cli {
$this->debug['getopt_return'] = $o;
foreach ($this->_args as $letter => $arg) {
- /* @var Cli\Arg $arg */
+ /* @var Arg $arg */
$this->values[$letter] = false;
if (isset($o[$letter])) {
if (is_bool($o[$letter])) {
@@ -295,7 +298,7 @@ class Cli {
{
$r = "\n";
foreach ($this->_args as $letter => $arg) {
- /* @var Cli\Arg $arg */
+ /* @var Arg $arg */
$desc = $arg->getDescription();
$flag = " -$letter ";
if ($arg->mayHaveValue) {
diff --git a/inc/lib/minify/MrClay/Cli/Arg.php b/inc/lib/minify/MrClay/Cli/Arg.php
index 81146a7f..5fa59327 100755
--- a/inc/lib/minify/MrClay/Cli/Arg.php
+++ b/inc/lib/minify/MrClay/Cli/Arg.php
@@ -2,6 +2,8 @@
namespace MrClay\Cli;
+use BadMethodCallException;
+
/**
* An argument for a CLI app. This specifies the argument, what values it expects and
* how it's treated during validation.
@@ -150,7 +152,7 @@ class Arg {
* @param string $name
* @param array $args
* @return Arg
- * @throws \BadMethodCallException
+ * @throws BadMethodCallException
*/
public function __call($name, array $args = array())
{
@@ -160,7 +162,7 @@ class Arg {
$this->spec['mustHaveValue'] = true;
}
} else {
- throw new \BadMethodCallException('Method does not exist');
+ throw new BadMethodCallException('Method does not exist');
}
return $this;
}
diff --git a/inc/lib/webm/posthandler.php b/inc/lib/webm/posthandler.php
index 50adbe80..1f831f5a 100644
--- a/inc/lib/webm/posthandler.php
+++ b/inc/lib/webm/posthandler.php
@@ -5,43 +5,43 @@
function postHandler($post) {
global $board, $config;
- if ($post->has_file && $post->extension == 'webm') {
+ if ($post->has_file) foreach ($post->files as &$file) if ($file->extension == 'webm') {
require_once dirname(__FILE__) . '/videodata.php';
- $videoDetails = videoData($post->file_path);
+ $videoDetails = videoData($file->file_path);
if (!isset($videoDetails['container']) || $videoDetails['container'] != 'webm') return "not a WebM file";
// Set thumbnail
- $thumbName = $board['dir'] . $config['dir']['thumb'] . $post->file_id . '.webm';
+ $thumbName = $board['dir'] . $config['dir']['thumb'] . $file->file_id . '.webm';
if ($config['spoiler_images'] && isset($_POST['spoiler'])) {
// Use spoiler thumbnail
- $post->thumb = 'spoiler';
+ $file->thumb = 'spoiler';
$size = @getimagesize($config['spoiler_image']);
- $post->thumbwidth = $size[0];
- $post->thumbheight = $size[1];
+ $file->thumbwidth = $size[0];
+ $file->thumbheight = $size[1];
} elseif (isset($videoDetails['frame']) && $thumbFile = fopen($thumbName, 'wb')) {
// Use single frame from video as pseudo-thumbnail
fwrite($thumbFile, $videoDetails['frame']);
fclose($thumbFile);
- $post->thumb = $post->file_id . '.webm';
+ $file->thumb = $file->file_id . '.webm';
} else {
// Fall back to file thumbnail
- $post->thumb = 'file';
+ $file->thumb = 'file';
}
unset($videoDetails['frame']);
// Set width and height
if (isset($videoDetails['width']) && isset($videoDetails['height'])) {
- $post->width = $videoDetails['width'];
- $post->height = $videoDetails['height'];
- if ($post->thumb != 'file' && $post->thumb != 'spoiler') {
+ $file->width = $videoDetails['width'];
+ $file->height = $videoDetails['height'];
+ if ($file->thumb != 'file' && $file->thumb != 'spoiler') {
$thumbMaxWidth = $post->op ? $config['thumb_op_width'] : $config['thumb_width'];
$thumbMaxHeight = $post->op ? $config['thumb_op_height'] : $config['thumb_height'];
if ($videoDetails['width'] > $thumbMaxWidth || $videoDetails['height'] > $thumbMaxHeight) {
- $post->thumbwidth = min($thumbMaxWidth, intval(round($videoDetails['width'] * $thumbMaxHeight / $videoDetails['height'])));
- $post->thumbheight = min($thumbMaxHeight, intval(round($videoDetails['height'] * $thumbMaxWidth / $videoDetails['width'])));
+ $file->thumbwidth = min($thumbMaxWidth, intval(round($videoDetails['width'] * $thumbMaxHeight / $videoDetails['height'])));
+ $file->thumbheight = min($thumbMaxHeight, intval(round($videoDetails['height'] * $thumbMaxWidth / $videoDetails['width'])));
} else {
- $post->thumbwidth = $videoDetails['width'];
- $post->thumbheight = $videoDetails['height'];
+ $file->thumbwidth = $videoDetails['width'];
+ $file->thumbheight = $videoDetails['height'];
}
}
}
diff --git a/inc/mod/pages.php b/inc/mod/pages.php
index 05a24865..9b9d3127 100644
--- a/inc/mod/pages.php
+++ b/inc/mod/pages.php
@@ -962,17 +962,22 @@ function mod_ban_appeals() {
if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
if (openBoard($ban['post']['board'])) {
- $query = query(sprintf("SELECT `thumb`, `file` FROM ``posts_%s`` WHERE `id` = " .
+ $query = query(sprintf("SELECT `num_files`, `files` FROM ``posts_%s`` WHERE `id` = " .
(int)$ban['post']['id'], $board['uri']));
if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
+ $_post['files'] = $_post['files'] ? json_decode($_post['files']) : array();
$ban['post'] = array_merge($ban['post'], $_post);
} else {
- $ban['post']['file'] = 'deleted';
- $ban['post']['thumb'] = false;
+ $ban['post']['files'] = array(array());
+ $ban['post']['files'][0]['file'] = 'deleted';
+ $ban['post']['files'][0]['thumb'] = false;
+ $ban['post']['num_files'] = 1;
}
} else {
- $ban['post']['file'] = 'deleted';
- $ban['post']['thumb'] = false;
+ $ban['post']['files'] = array(array());
+ $ban['post']['files'][0]['file'] = 'deleted';
+ $ban['post']['files'][0]['thumb'] = false;
+ $ban['post']['num_files'] = 1;
}
if ($ban['post']['thread']) {
@@ -1096,13 +1101,13 @@ function mod_move_reply($originBoard, $postID) {
$post['op'] = true;
}
- if ($post['file']) {
+ if ($post['files']) {
+ $post['files'] = json_decode($post['files'], TRUE);
$post['has_file'] = true;
- $post['width'] = &$post['filewidth'];
- $post['height'] = &$post['fileheight'];
-
- $file_src = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $post['file'];
- $file_thumb = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $post['thumb'];
+ foreach ($post['files'] as $i => &$file) {
+ $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
+ $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
+ }
} else {
$post['has_file'] = false;
}
@@ -1117,10 +1122,12 @@ function mod_move_reply($originBoard, $postID) {
$newID = post($post);
if ($post['has_file']) {
- // move the image
- rename($file_src, sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $post['file']);
- if ($post['thumb'] != 'spoiler') { //trying to move/copy the spoiler thumb raises an error
- rename($file_thumb, sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $post['thumb']);
+ foreach ($post['files'] as $i => &$file) {
+ // move the image
+ rename($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
+ if ($file['thumb'] != 'spoiler') { //trying to move/copy the spoiler thumb raises an error
+ rename($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
+ }
}
}
@@ -1193,13 +1200,15 @@ function mod_move($originBoard, $postID) {
// indicate that the post is a thread
$post['op'] = true;
- if ($post['file']) {
+ if ($post['files']) {
+ $post['files'] = json_decode($post['files'], TRUE);
$post['has_file'] = true;
- $post['width'] = &$post['filewidth'];
- $post['height'] = &$post['fileheight'];
-
- $file_src = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $post['file'];
- $file_thumb = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $post['thumb'];
+ foreach ($post['files'] as $i => &$file) {
+ if ($file['file'] === 'deleted')
+ continue;
+ $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
+ $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
+ }
} else {
$post['has_file'] = false;
}
@@ -1215,9 +1224,12 @@ function mod_move($originBoard, $postID) {
if ($post['has_file']) {
// copy image
- $clone($file_src, sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $post['file']);
- if (!in_array($post['thumb'], array('spoiler', 'deleted', 'file')))
- $clone($file_thumb, sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $post['thumb']);
+ foreach ($post['files'] as $i => &$file) {
+ if ($file['file'] !== 'deleted')
+ $clone($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
+ if (isset($file['thumb']) && !in_array($file['thumb'], array('spoiler', 'deleted', 'file')))
+ $clone($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
+ }
}
// go back to the original board to fetch replies
@@ -1233,13 +1245,13 @@ function mod_move($originBoard, $postID) {
$post['mod'] = true;
$post['thread'] = $newID;
- if ($post['file']) {
+ if ($post['files']) {
+ $post['files'] = json_decode($post['files'], TRUE);
$post['has_file'] = true;
- $post['width'] = &$post['filewidth'];
- $post['height'] = &$post['fileheight'];
-
- $post['file_src'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $post['file'];
- $post['file_thumb'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $post['thumb'];
+ foreach ($post['files'] as $i => &$file) {
+ $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
+ $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
+ }
} else {
$post['has_file'] = false;
}
@@ -1274,14 +1286,16 @@ function mod_move($originBoard, $postID) {
$post['op'] = false;
$post['tracked_cites'] = markup($post['body'], true);
- // insert reply
- $newIDs[$post['id']] = $newPostID = post($post);
-
if ($post['has_file']) {
// copy image
- $clone($post['file_src'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $post['file']);
- $clone($post['file_thumb'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $post['thumb']);
+ foreach ($post['files'] as $i => &$file) {
+ $clone($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
+ $clone($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
+ }
}
+ // insert reply
+ $newIDs[$post['id']] = $newPostID = post($post);
+
if (!empty($post['tracked_cites'])) {
$insert_rows = array();
@@ -1513,7 +1527,7 @@ function mod_delete($board, $post) {
header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
}
-function mod_deletefile($board, $post) {
+function mod_deletefile($board, $post, $file) {
global $config, $mod;
if (!openBoard($board))
@@ -1523,7 +1537,7 @@ function mod_deletefile($board, $post) {
error($config['error']['noaccess']);
// Delete file
- deleteFile($post);
+ deleteFile($post, TRUE, $file);
// Record the action
modLog("Deleted file from post #{$post}");
@@ -1536,28 +1550,30 @@ function mod_deletefile($board, $post) {
header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
}
-function mod_spoiler_image($board, $post) {
+function mod_spoiler_image($board, $post, $file) {
global $config, $mod;
-
+
if (!openBoard($board))
error($config['error']['noboard']);
-
+
if (!hasPermission($config['mod']['spoilerimage'], $board))
error($config['error']['noaccess']);
- // Delete file
- $query = prepare(sprintf("SELECT `thumb`, `thread` FROM ``posts_%s`` WHERE id = :id", $board));
+ // Delete file thumbnail
+ $query = prepare(sprintf("SELECT `files`, `thread` FROM ``posts_%s`` WHERE id = :id", $board));
$query->bindValue(':id', $post, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$result = $query->fetch(PDO::FETCH_ASSOC);
+ $files = json_decode($result['files']);
- file_unlink($board . '/' . $config['dir']['thumb'] . $result['thumb']);
-
+ file_unlink($board . '/' . $config['dir']['thumb'] . $files[$file]->thumb);
+ $files[$file]->thumb = 'spoiler';
+ $files[$file]->thumbheight = 128;
+ $files[$file]->thumbwidth = 128;
+
// Make thumbnail spoiler
- $query = prepare(sprintf("UPDATE ``posts_%s`` SET `thumb` = :thumb, `thumbwidth` = :thumbwidth, `thumbheight` = :thumbheight WHERE `id` = :id", $board));
- $query->bindValue(':thumb', "spoiler");
- $query->bindValue(':thumbwidth', 128, PDO::PARAM_INT);
- $query->bindValue(':thumbheight', 128, PDO::PARAM_INT);
+ $query = prepare(sprintf("UPDATE ``posts_%s`` SET `files` = :files WHERE `id` = :id", $board));
+ $query->bindValue(':files', json_encode($files));
$query->bindValue(':id', $post, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
@@ -1572,7 +1588,7 @@ function mod_spoiler_image($board, $post) {
// Rebuild themes
rebuildThemes('post-delete', $board);
-
+
// Redirect
header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
}
@@ -2213,6 +2229,68 @@ function mod_report_dismiss($id, $all = false) {
header('Location: ?/reports', true, $config['redirect_http']);
}
+function mod_recent_posts($lim) {
+ global $config, $mod, $pdo;
+
+ if (!hasPermission($config['mod']['recent']))
+ error($config['error']['noaccess']);
+
+ $limit = (is_numeric($lim))? $lim : 25;
+
+ $mod_boards = array();
+ $boards = listBoards();
+
+ //if not all boards
+ if ($mod['boards'][0]!='*') {
+ foreach ($boards as $board) {
+ if (in_array($board['uri'], $mod['boards']))
+ $mod_boards[] = $board;
+ }
+ } else {
+ $mod_boards = $boards;
+ }
+
+ // Manually build an SQL query
+ $query = 'SELECT * FROM (';
+ foreach ($mod_boards as $board) {
+ $query .= sprintf('SELECT *, %s AS `board` FROM ``posts_%s`` UNION ALL ', $pdo->quote($board['uri']), $board['uri']);
+ }
+ // Remove the last "UNION ALL" seperator and complete the query
+ $query = preg_replace('/UNION ALL $/', ') AS `all_posts` ORDER BY `time` DESC LIMIT ' . $limit, $query);
+ $query = query($query) or error(db_error());
+ $posts = $query->fetchAll(PDO::FETCH_ASSOC);
+
+ $body = '<h4>Viewing last '.$limit.' posts</h4>
+ <p>View <a href="?/recent/25"> 25 </a>|<a href="?/recent/50"> 50 </a>|<a href="?/recent/100"> 100 </a></p>
+ <a href="javascript:void(0)" id="erase-local-data" style="float:right; clear:both">Erase local data</a></div>';
+ foreach ($posts as $post) {
+ openBoard($post['board']);
+ if (!$post['thread']) {
+ // Still need to fix this:
+ $po = new Thread($post, '?/', $mod, false);
+ $string = $po->build(true);
+ $string = '<div class="post-wrapper" data-board="'.$post['board'].'"><hr/><a class="eita-link" id="eita-'.$post['board'].'-'.$post['id'].'" href="?/'.$post['board'].'/res/'.$post['id'].'.html#'.$post['id'].'">/'.$post['board'].'/'.$post['id'].'</a><br>' . $string;
+ } else {
+ $po = new Post($post, '?/', $mod);
+ $string = $po->build(true);
+ $string = '<div class="post-wrapper" data-board="'.$post['board'].'"><hr/><a class="eita-link" id="eita-'.$post['board'].'-'.$post['id'].'" href="?/'.$post['board'].'/res/'.$post['thread'].'.html#'.$post['id'].'">/'.$post['board'].'/'.$post['id'].'</a><br>' . $string;
+ }
+ $body .= $string . '</div>';
+ }
+
+ echo Element('page.html', array(
+ 'config' => $config,
+ 'mod' => $mod,
+ 'hide_dashboard_link' => true,
+ 'title' => _('Recent posts'),
+ 'subtitle' => '',
+ 'nojavascript' => false,
+ 'is_recent_posts' => true,
+ 'body' => $body
+ )
+ );
+
+}
function mod_config($board_config = false) {
global $config, $mod, $board;