Merge pull request #1 from shish/master

Keeping up to date
This commit is contained in:
Mik-chan 2018-02-20 00:33:12 +03:00 committed by GitHub
commit 4721d1ee3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
326 changed files with 7611 additions and 49229 deletions

13
.gitignore vendored
View File

@ -5,7 +5,12 @@ thumbs
!lib/images !lib/images
*.phar *.phar
*.sqlite *.sqlite
/lib/vendor/
#Composer
composer.phar
composer.lock
/vendor/
# Created by http://www.gitignore.io # Created by http://www.gitignore.io
@ -20,13 +25,6 @@ Desktop.ini
# Recycle Bin used on file shares # Recycle Bin used on file shares
$RECYCLE.BIN/ $RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
### OSX ### ### OSX ###
.DS_Store .DS_Store
.AppleDouble .AppleDouble
@ -35,7 +33,6 @@ $RECYCLE.BIN/
# Icon must ends with two \r. # Icon must ends with two \r.
Icon Icon
# Thumbnails # Thumbnails
._* ._*

View File

@ -3,11 +3,16 @@
</IfModule> </IfModule>
<FilesMatch "\.(sqlite|sdb|s3db|db)$"> <FilesMatch "\.(sqlite|sdb|s3db|db)$">
Deny from all <IfModule mod_authz_host.c>
Require all denied
</IfModule>
<IfModule !mod_authz_host.c>
Deny from all
</IfModule>
</FilesMatch> </FilesMatch>
<IfModule mod_rewrite.c> <IfModule mod_rewrite.c>
RewriteEngine on RewriteEngine On
# rather than link to images/ha/hash and have an ugly filename, # rather than link to images/ha/hash and have an ugly filename,
# we link to images/hash/tags.ext; mod_rewrite splits things so # we link to images/hash/tags.ext; mod_rewrite splits things so
@ -20,14 +25,6 @@
RewriteRule ^(.*)$ index.php?q=$1&%{QUERY_STRING} [L] RewriteRule ^(.*)$ index.php?q=$1&%{QUERY_STRING} [L]
</IfModule> </IfModule>
<IfModule mod_php5.c>
php_flag register_globals 0
php_flag magic_quotes_gpc 0
php_flag magic_quotes_runtime 0
</IfModule>
DefaultType image/jpeg
<IfModule mod_expires.c> <IfModule mod_expires.c>
ExpiresActive On ExpiresActive On
<FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|css|js))$"> <FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|css|js))$">
@ -40,7 +37,31 @@ DefaultType image/jpeg
#ExpiresByType text/plain "now" #ExpiresByType text/plain "now"
</IfModule> </IfModule>
<ifmodule mod_deflate.c> <IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/x-javascript application/javascript AddOutputFilterByType DEFLATE application/x-javascript application/javascript
</ifmodule> </IfModule>
#EXT: handle_pixel
AddType image/jpeg jpg jpeg
AddType image/gif gif
AddType image/png png
#EXT: handle_ico
AddType image/x-icon ico ani cur
#EXT: handle_flash
AddType application/x-shockwave-flash swf
#EXT: handle_mp3
AddType audio/mpeg mp3
#EXT: handle_svg
AddType image/svg+xml svg svgz
#EXT: handle_video
AddType video/x-flv flv
AddType video/mp4 f4v f4p m4v mp4
AddType audio/mp4 f4a f4b m4a
AddType video/ogg ogv
AddType video/webm webm

View File

@ -3,7 +3,7 @@ imports:
- php - php
filter: filter:
excluded_paths: [lib/*,ext/tagger/script.js,ext/chatbox/*] excluded_paths: [lib/*,ext/*/lib/*,ext/tagger/script.js,ext/chatbox/*]
tools: tools:
external_code_coverage: true external_code_coverage: true

View File

@ -1,43 +1,62 @@
language: php language: php
sudo: false
php: php:
- 5.4 - 5.6
- 5.5 - 7.0
- 5.6 - 7.1
- nightly
sudo: false
env: env:
matrix: matrix:
- DB=mysql - DB=mysql
- DB=pgsql - DB=pgsql
- DB=sqlite - DB=sqlite
allow_failures:
- DB=sqlite
cache:
directories:
- vendor
- $HOME/.composer/cache
before_install:
- travis_retry composer self-update && composer --version #travis is bad at updating composer
- if [ -n "$GH_TOKEN" ]; then composer config github-oauth.github.com ${GH_TOKEN}; fi;
install: install:
- mkdir -p data/config - mkdir -p data/config
- if [[ "$DB" == "pgsql" ]]; then psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres; fi - |
- if [[ "$DB" == "pgsql" ]]; then psql -c "CREATE DATABASE shimmie;" -U postgres; fi if [[ "$DB" == "pgsql" ]]; then
- if [[ "$DB" == "pgsql" ]]; then echo '<?php define("DATABASE_DSN", "pgsql:user=postgres;password=;host=;dbname=shimmie");' > data/config/auto_install.conf.php ; fi psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres ;
- if [[ "$DB" == "mysql" ]]; then mysql -e "SET GLOBAL general_log = 'ON';" -uroot; fi psql -c "CREATE DATABASE shimmie;" -U postgres ;
- if [[ "$DB" == "mysql" ]]; then mysql -e "CREATE DATABASE shimmie;" -uroot; fi echo '<?php define("DATABASE_DSN", "pgsql:user=postgres;password=;host=;dbname=shimmie");' > data/config/auto_install.conf.php ;
- if [[ "$DB" == "mysql" ]]; then echo '<?php define("DATABASE_DSN", "mysql:user=root;password=;host=localhost;dbname=shimmie");' > data/config/auto_install.conf.php ; fi fi
- if [[ "$DB" == "sqlite" ]]; then echo '<?php define("DATABASE_DSN", "sqlite:shimmie.sqlite");' > data/config/auto_install.conf.php ; fi - |
- wget https://scrutinizer-ci.com/ocular.phar if [[ "$DB" == "mysql" ]]; then
mysql -e "SET GLOBAL general_log = 'ON';" -uroot ;
mysql -e "CREATE DATABASE shimmie;" -uroot ;
echo '<?php define("DATABASE_DSN", "mysql:user=root;password=;host=localhost;dbname=shimmie");' > data/config/auto_install.conf.php ;
fi
- if [[ "$DB" == "sqlite" ]]; then echo '<?php define("DATABASE_DSN", "sqlite:shimmie.sqlite");' > data/config/auto_install.conf.php ; fi
- composer install
- php install.php
script: script:
- php install.php - vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
- phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
after_failure: after_failure:
- head -n 100 data/config/* - head -n 100 data/config/*
- ls /var/run/mysql* - ls /var/run/mysql*
- ls /var/log/*mysql* # All of the below commands require sudo, which we can't use without losing some speed & caching.
- cat /var/log/mysql.err # SEE: https://docs.travis-ci.com/user/workers/container-based-infrastructure/
- cat /var/log/mysql.log # - ls /var/log/*mysql*
- cat /var/log/mysql/error.log # - cat /var/log/mysql.err
- cat /var/log/mysql/slow.log # - cat /var/log/mysql.log
- ls /var/log/postgresql # - cat /var/log/mysql/error.log
- cat /var/log/postgresql/postgresql* # - cat /var/log/mysql/slow.log
# - ls /var/log/postgresql
# - cat /var/log/postgresql/postgresql*
after_script: after_script:
- php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover - wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover

View File

@ -1,12 +1,12 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 2, June 1991 Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc. Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
Preamble Preamble
The licenses for most software are designed to take away your The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public freedom to share and change it. By contrast, the GNU General Public
@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to the GNU Lesser General Public License instead.) You can apply it to
your programs, too. your programs, too.
When we speak of free software, we are referring to freedom, not When we speak of free software, we are referring to freedom, not
@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and The precise terms and conditions for copying, distribution and
modification follow. modification follow.
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains 0. This License applies to any program or other work which contains
@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on does not normally print such an announcement, your work based on
the Program is not required to print an announcement.) the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program, identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in and can be reasonably considered independent and separate works in
@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not distribution of the source code, even though third parties are not
compelled to copy the source along with the object code. compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program 4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is otherwise to copy, modify, sublicense or distribute the Program is
@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License. be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in 8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License original copyright holder who places the Program under this License
@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally. of promoting the sharing and reuse of software generally.
NO WARRANTY NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES. POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it possible use to the public, the best way to achieve this is to make it
@ -290,8 +290,8 @@ to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> {description}
Copyright (C) <year> <name of author> Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License along
along with this program; if not, write to the Free Software with this program; if not, write to the Free Software Foundation, Inc.,
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
@ -330,11 +329,11 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker. `Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989 {signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice Ty Coon, President of Vice
This General Public License does not permit incorporating your program into This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. Public License instead of this License.

View File

@ -11,6 +11,14 @@
# Shimmie # Shimmie
[![Build Status](https://travis-ci.org/shish/shimmie2.svg?branch=master)](https://travis-ci.org/shish/shimmie2) [![Build Status](https://travis-ci.org/shish/shimmie2.svg?branch=master)](https://travis-ci.org/shish/shimmie2)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/shish/shimmie2/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/shish/shimmie2/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=master)
(master)
[![Build Status](https://travis-ci.org/shish/shimmie2.svg?branch=develop)](https://travis-ci.org/shish/shimmie2)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/shish/shimmie2/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=develop)
[![Code Coverage](https://scrutinizer-ci.com/g/shish/shimmie2/badges/coverage.png?b=develop)](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=develop)
(develop)
This is the main branch of Shimmie, if you know anything at all about running This is the main branch of Shimmie, if you know anything at all about running
websites, this is the version to use. websites, this is the version to use.
@ -21,18 +29,27 @@ check out one of the versioned branches.
# Requirements # Requirements
- MySQL/MariaDB 5.1+ (with experimental support for PostgreSQL 9+ and SQLite 3) - MySQL/MariaDB 5.1+ (with experimental support for PostgreSQL 9+ and SQLite 3)
- PHP 5.4.8+ - [Stable PHP](https://en.wikipedia.org/wiki/PHP#Release_history) (5.6+ as of writing)
- GD or ImageMagick - GD or ImageMagick
# Installation # Installation
1. Create a blank database 1. Download the latest release under [Releases](https://github.com/shish/shimmie2/releases).
2. Unzip shimmie into a folder on the web host 2. Unzip shimmie into a folder on the web host
3. Visit the folder with a web browser 3. Create a blank database
4. Enter the location of the database 4. Visit the folder with a web browser
5. Click "install". Hopefully you'll end up at the welcome screen; if 5. Enter the location of the database
6. Click "install". Hopefully you'll end up at the welcome screen; if
not, you should be given instructions on how to fix any errors~ not, you should be given instructions on how to fix any errors~
# Installation (Development)
1. Download shimmie via the "Download Zip" button on the [develop](https://github.com/shish/shimmie2/tree/develop) branch.
2. Unzip shimmie into a folder on the web host
3. Install [Composer](https://getcomposer.org/). (If you don't already have it)
4. Run `composer install` in the shimmie folder.
5. Follow instructions noted in "Installation" starting from step 3.
## Upgrade from 2.3.X ## Upgrade from 2.3.X
1. Backup your current files and database! 1. Backup your current files and database!
@ -143,7 +160,7 @@ Issue/Bug tracker: http://github.com/shish/shimmie2/issues
All code is released under the [GNU GPL Version 2](http://www.gnu.org/licenses/gpl-2.0.html) unless mentioned otherwise. All code is released under the [GNU GPL Version 2](http://www.gnu.org/licenses/gpl-2.0.html) unless mentioned otherwise.
If you give shimmie to someone else, you have to give them the source (which should be easy, as PHP If you give shimmie to someone else, you have to give them the source (which
is an interpreted language...). If you want to add customisations to your own should be easy, as PHP is an interpreted language...). If you want to add
site, then those customisations belong to you, and you can do what you want customisations to your own site, then those customisations belong to you,
with them. and you can do what you want with them.

68
composer.json Normal file
View File

@ -0,0 +1,68 @@
{
"type" : "project",
"license" : "GPL-2.0",
"minimum-stability" : "dev",
"repositories" : [
{
"type": "composer",
"url": "https://asset-packagist.org"
},
{
"type" : "package",
"package" : {
"name" : "ifixit/php-akismet",
"version" : "1.1",
"source" : {
"url" : "https://github.com/iFixit/php-akismet.git",
"type" : "git",
"reference" : "fd4ff50eb577457c1b7b887401663e91e77625ae"
}
}
}
],
"require" : {
"php" : ">=5.6",
"flexihash/flexihash" : "^2.0.0",
"ifixit/php-akismet" : "1.*",
"google/recaptcha" : "~1.1",
"dapphp/securimage" : "3.6.*",
"bower-asset/jquery" : "1.12.3",
"bower-asset/jquery-timeago" : "1.5.2",
"bower-asset/tablesorter" : "dev-master",
"bower-asset/mediaelement" : "2.21.1",
"bower-asset/js-cookie" : "2.1.1"
},
"require-dev" : {
"phpunit/phpunit" : "5.*"
},
"vendor-copy": {
"vendor/bower-asset/jquery/dist/jquery.min.js" : "lib/vendor/js/jquery-1.12.3.min.js",
"vendor/bower-asset/jquery/dist/jquery.min.map" : "lib/vendor/js/jquery-1.12.3.min.map",
"vendor/bower-asset/jquery-timeago/jquery.timeago.js" : "lib/vendor/js/jquery.timeago.js",
"vendor/bower-asset/tablesorter/jquery.tablesorter.min.js" : "lib/vendor/js/jquery.tablesorter.min.js",
"vendor/bower-asset/mediaelement/build/flashmediaelement.swf" : "lib/vendor/swf/flashmediaelement.swf",
"vendor/bower-asset/js-cookie/src/js.cookie.js" : "lib/vendor/js/js.cookie.js"
},
"scripts": {
"pre-install-cmd" : [
"php -r \"array_map('unlink', array_merge(glob('lib/vendor/js/j*.{js,map}', GLOB_BRACE), glob('lib/vendor/css/*.css'), glob('lib/vendor/swf/*.swf')));\""
],
"pre-update-cmd" : [
"php -r \"array_map('unlink', array_merge(glob('lib/vendor/js/j*.{js,map}', GLOB_BRACE), glob('lib/vendor/css/*.css'), glob('lib/vendor/swf/*.swf')));\""
],
"post-install-cmd" : [
"php -r \"array_map('copy', array_keys(json_decode(file_get_contents('composer.json'), TRUE)['vendor-copy']), json_decode(file_get_contents('composer.json'), TRUE)['vendor-copy']);\""
],
"post-update-cmd" : [
"php -r \"array_map('copy', array_keys(json_decode(file_get_contents('composer.json'), TRUE)['vendor-copy']), json_decode(file_get_contents('composer.json'), TRUE)['vendor-copy']);\""
]
}
}

1593
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,8 @@ global $config, $database, $user, $page;
require_once "core/sys_config.inc.php"; require_once "core/sys_config.inc.php";
require_once "core/util.inc.php"; require_once "core/util.inc.php";
require_once "lib/context.php"; require_once "lib/context.php";
require_once "vendor/autoload.php";
require_once "core/imageboard.pack.php";
// set up and purify the environment // set up and purify the environment
_version_check(); _version_check();
@ -16,17 +18,17 @@ _sanitise_environment();
// load base files // load base files
ctx_log_start("Opening files"); ctx_log_start("Opening files");
$files = array_merge( $_shm_files = array_merge(
zglob("core/*.php"), zglob("core/*.php"),
zglob("ext/{".ENABLED_EXTS."}/main.php") zglob("ext/{".ENABLED_EXTS."}/main.php")
); );
foreach($files as $filename) { foreach($_shm_files as $_shm_filename) {
if(basename($filename)[0] != "_") { if(basename($_shm_filename)[0] != "_") {
require_once $filename; require_once $_shm_filename;
} }
} }
unset($files); unset($_shm_files);
unset($filename); unset($_shm_filename);
ctx_log_endok(); ctx_log_endok();
// connect to the database // connect to the database

View File

@ -54,7 +54,7 @@ class BaseThemelet {
$h_view_link = make_link('post/view/'.$i_id); $h_view_link = make_link('post/view/'.$i_id);
$h_thumb_link = $image->get_thumb_link(); $h_thumb_link = $image->get_thumb_link();
$h_tip = html_escape($image->get_tooltip()); $h_tip = html_escape($image->get_tooltip());
$h_tags = strtolower($image->get_tag_list()); $h_tags = html_escape(strtolower($image->get_tag_list()));
$extArr = array_flip(array('swf', 'svg', 'mp3')); //List of thumbless filetypes $extArr = array_flip(array('swf', 'svg', 'mp3')); //List of thumbless filetypes
if(!isset($extArr[$image->ext])){ if(!isset($extArr[$image->ext])){
@ -67,7 +67,7 @@ class BaseThemelet {
$custom_classes = ""; $custom_classes = "";
if(class_exists("Relationships")){ if(class_exists("Relationships")){
if(property_exists($image, 'parent_id') && $image->parent_id !== NULL){ $custom_classes .= "shm-thumb-has_parent "; } if(property_exists($image, 'parent_id') && $image->parent_id !== NULL){ $custom_classes .= "shm-thumb-has_parent "; }
if(property_exists($image, 'has_children') && $image->has_children == TRUE){ $custom_classes .= "shm-thumb-has_child "; } if(property_exists($image, 'has_children') && bool_escape($image->has_children)){ $custom_classes .= "shm-thumb-has_child "; }
} }
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>". return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>".

View File

@ -44,6 +44,14 @@ class Block {
*/ */
public $id; public $id;
/**
* Should this block count as content for the sake of
* the 404 handler
*
* @var boolean
*/
public $is_content = true;
/** /**
* Construct a block. * Construct a block.
* *
@ -58,7 +66,11 @@ class Block {
$this->body = $body; $this->body = $body;
$this->section = $section; $this->section = $section;
$this->position = $position; $this->position = $position;
$this->id = preg_replace('/[^\w]/', '',str_replace(' ', '_', is_null($id) ? (is_null($header) ? md5($body) : $header) . $section : $id));
if(is_null($id)) {
$id = (empty($header) ? md5($body) : $header) . $section;
}
$this->id = preg_replace('/[^\w]/', '',str_replace(' ', '_', $id));
} }
/** /**

View File

@ -151,7 +151,7 @@ interface Config {
* left to the concrete implementation * left to the concrete implementation
*/ */
abstract class BaseConfig implements Config { abstract class BaseConfig implements Config {
var $values = array(); public $values = array();
/** /**
* @param string $name * @param string $name
@ -366,8 +366,8 @@ class StaticConfig extends BaseConfig {
* \endcode * \endcode
*/ */
class DatabaseConfig extends BaseConfig { class DatabaseConfig extends BaseConfig {
/** @var \Database|null */ /** @var Database */
var $database = null; private $database = null;
/** /**
* Load the config table from a database. * Load the config table from a database.

View File

@ -263,11 +263,34 @@ class SQLite extends DBEngine {
// }}} // }}}
// {{{ cache engines // {{{ cache engines
interface CacheEngine { interface CacheEngine {
/**
* @param string $key
* @return mixed
*/
public function get($key); public function get($key);
/**
* @param string $key
* @param mixed $val
* @param integer $time
* @return void
*/
public function set($key, $val, $time=0); public function set($key, $val, $time=0);
/**
* @return void
*/
public function delete($key); public function delete($key);
/**
* @return integer
*/
public function get_hits(); public function get_hits();
/**
* @return integer
*/
public function get_misses(); public function get_misses();
} }
class NoCache implements CacheEngine { class NoCache implements CacheEngine {
@ -291,13 +314,8 @@ class MemcacheCache implements CacheEngine {
*/ */
public function __construct($args) { public function __construct($args) {
$hp = explode(":", $args); $hp = explode(":", $args);
if(class_exists("Memcache")) { $this->memcache = new Memcache;
$this->memcache = new Memcache; @$this->memcache->pconnect($hp[0], $hp[1]);
@$this->memcache->pconnect($hp[0], $hp[1]);
}
else {
print "no memcache"; exit;
}
} }
/** /**
@ -324,7 +342,7 @@ class MemcacheCache implements CacheEngine {
/** /**
* @param string $key * @param string $key
* @param mixed $val * @param mixed $val
* @param int $time * @param integer $time
*/ */
public function set($key, $val, $time=0) { public function set($key, $val, $time=0) {
assert('!is_null($key)'); assert('!is_null($key)');
@ -355,9 +373,103 @@ class MemcacheCache implements CacheEngine {
*/ */
public function get_misses() {return $this->misses;} public function get_misses() {return $this->misses;}
} }
class MemcachedCache implements CacheEngine {
/** @var \Memcached|null */
public $memcache=null;
/** @var int */
private $hits=0;
/** @var int */
private $misses=0;
/**
* @param string $args
*/
public function __construct($args) {
$hp = explode(":", $args);
$this->memcache = new Memcached;
#$this->memcache->setOption(Memcached::OPT_COMPRESSION, False);
#$this->memcache->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP);
#$this->memcache->setOption(Memcached::OPT_PREFIX_KEY, phpversion());
$this->memcache->addServer($hp[0], $hp[1]);
}
/**
* @param string $key
* @return array|bool|string
*/
public function get($key) {
assert('!is_null($key)');
$key = urlencode($key);
$val = $this->memcache->get($key);
$res = $this->memcache->getResultCode();
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
$hit = $res == Memcached::RES_SUCCESS ? "hit" : "miss";
file_put_contents("data/cache.log", "Cache $hit: $key\n", FILE_APPEND);
}
if($res == Memcached::RES_SUCCESS) {
$this->hits++;
return $val;
}
else if($res == Memcached::RES_NOTFOUND) {
$this->misses++;
return false;
}
else {
error_log("Memcached error during get($key): $res");
}
}
/**
* @param string $key
* @param mixed $val
* @param int $time
*/
public function set($key, $val, $time=0) {
assert('!is_null($key)');
$key = urlencode($key);
$this->memcache->set($key, $val, $time);
$res = $this->memcache->getResultCode();
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND);
}
if($res != Memcached::RES_SUCCESS) {
error_log("Memcached error during set($key): $res");
}
}
/**
* @param string $key
*/
public function delete($key) {
assert('!is_null($key)');
$key = urlencode($key);
$this->memcache->delete($key);
$res = $this->memcache->getResultCode();
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND);
}
if($res != Memcached::RES_SUCCESS && $res != Memcached::RES_NOTFOUND) {
error_log("Memcached error during delete($key): $res");
}
}
/**
* @return int
*/
public function get_hits() {return $this->hits;}
/**
* @return int
*/
public function get_misses() {return $this->misses;}
}
class APCCache implements CacheEngine { class APCCache implements CacheEngine {
var $hits=0, $misses=0; public $hits=0, $misses=0;
public function __construct($args) { public function __construct($args) {
// $args is not used, but is passed in when APC cache is created. // $args is not used, but is passed in when APC cache is created.
@ -401,6 +513,10 @@ class Database {
* @var null|PDO * @var null|PDO
*/ */
private $db = null; private $db = null;
/**
* @var float
*/
public $dbtime = 0.0; public $dbtime = 0.0;
/** /**
@ -439,10 +555,13 @@ class Database {
private function connect_cache() { private function connect_cache() {
$matches = array(); $matches = array();
if(defined("CACHE_DSN") && CACHE_DSN && preg_match("#(memcache|apc)://(.*)#", CACHE_DSN, $matches)) { if(defined("CACHE_DSN") && CACHE_DSN && preg_match("#(memcache|memcached|apc)://(.*)#", CACHE_DSN, $matches)) {
if($matches[1] == "memcache") { if($matches[1] == "memcache") {
$this->cache = new MemcacheCache($matches[2]); $this->cache = new MemcacheCache($matches[2]);
} }
else if($matches[1] == "memcached") {
$this->cache = new MemcachedCache($matches[2]);
}
else if($matches[1] == "apc") { else if($matches[1] == "apc") {
$this->cache = new APCCache($matches[2]); $this->cache = new APCCache($matches[2]);
} }
@ -509,7 +628,7 @@ class Database {
} }
/** /**
* @return bool * @return boolean|null
* @throws SCoreException * @throws SCoreException
*/ */
public function commit() { public function commit() {
@ -525,7 +644,7 @@ class Database {
} }
/** /**
* @return bool * @return boolean|null
* @throws SCoreException * @throws SCoreException
*/ */
public function rollback() { public function rollback() {
@ -566,19 +685,20 @@ class Database {
return $this->engine->name; return $this->engine->name;
} }
/**
* @param null|PDO $db
* @param string $sql
*/
private function count_execs($db, $sql, $inputarray) { private function count_execs($db, $sql, $inputarray) {
if ((defined('DEBUG_SQL') && DEBUG_SQL === true) || (!defined('DEBUG_SQL') && @$_GET['DEBUG_SQL'])) { if((DEBUG_SQL === true) || (is_null(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) {
$fp = @fopen("data/sql.log", "a"); $sql = trim(preg_replace('/\s+/msi', ' ', $sql));
if($fp) { if(isset($inputarray) && is_array($inputarray) && !empty($inputarray)) {
$sql = trim(preg_replace('/\s+/msi', ' ', $sql)); $text = $sql." -- ".join(", ", $inputarray)."\n";
if(isset($inputarray) && is_array($inputarray) && !empty($inputarray)) {
fwrite($fp, $sql." -- ".join(", ", $inputarray)."\n");
}
else {
fwrite($fp, $sql."\n");
}
fclose($fp);
} }
else {
$text = $sql."\n";
}
file_put_contents("data/sql.log", $text, FILE_APPEND);
} }
if(!is_array($inputarray)) $this->query_count++; if(!is_array($inputarray)) $this->query_count++;
# handle 2-dimensional input arrays # handle 2-dimensional input arrays
@ -586,6 +706,14 @@ class Database {
else $this->query_count++; else $this->query_count++;
} }
private function count_time($method, $start) {
if((DEBUG_SQL === true) || (is_null(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) {
$text = $method.":".(microtime(true) - $start)."\n";
file_put_contents("data/sql.log", $text, FILE_APPEND);
}
$this->dbtime += microtime(true) - $start;
}
/** /**
* Execute an SQL query and return an PDO result-set. * Execute an SQL query and return an PDO result-set.
* *
@ -630,7 +758,7 @@ class Database {
public function get_all($query, $args=array()) { public function get_all($query, $args=array()) {
$_start = microtime(true); $_start = microtime(true);
$data = $this->execute($query, $args)->fetchAll(); $data = $this->execute($query, $args)->fetchAll();
$this->dbtime += microtime(true) - $_start; $this->count_time("get_all", $_start);
return $data; return $data;
} }
@ -644,7 +772,7 @@ class Database {
public function get_row($query, $args=array()) { public function get_row($query, $args=array()) {
$_start = microtime(true); $_start = microtime(true);
$row = $this->execute($query, $args)->fetch(); $row = $this->execute($query, $args)->fetch();
$this->dbtime += microtime(true) - $_start; $this->count_time("get_row", $_start);
return $row ? $row : null; return $row ? $row : null;
} }
@ -662,7 +790,7 @@ class Database {
foreach($stmt as $row) { foreach($stmt as $row) {
$res[] = $row[0]; $res[] = $row[0];
} }
$this->dbtime += microtime(true) - $_start; $this->count_time("get_col", $_start);
return $res; return $res;
} }
@ -680,7 +808,7 @@ class Database {
foreach($stmt as $row) { foreach($stmt as $row) {
$res[$row[0]] = $row[1]; $res[$row[0]] = $row[1];
} }
$this->dbtime += microtime(true) - $_start; $this->count_time("get_pairs", $_start);
return $res; return $res;
} }
@ -694,7 +822,7 @@ class Database {
public function get_one($query, $args=array()) { public function get_one($query, $args=array()) {
$_start = microtime(true); $_start = microtime(true);
$row = $this->execute($query, $args)->fetch(); $row = $this->execute($query, $args)->fetch();
$this->dbtime += microtime(true) - $_start; $this->count_time("get_one", $_start);
return $row[0]; return $row[0];
} }
@ -755,11 +883,11 @@ class Database {
class MockDatabase extends Database { class MockDatabase extends Database {
/** @var int */ /** @var int */
var $query_id = 0; private $query_id = 0;
/** @var array */ /** @var array */
var $responses = array(); private $responses = array();
/** @var \NoCache|null */ /** @var \NoCache|null */
var $cache = null; public $cache = null;
/** /**
* @param array $responses * @param array $responses
@ -786,35 +914,35 @@ class MockDatabase extends Database {
/** /**
* @param string $query * @param string $query
* @param array $args * @param array $args
* @return array|PDOStatement * @return PDOStatement
*/ */
public function get_all($query, $args=array()) {return $this->execute($query, $args);} public function get_all($query, $args=array()) {return $this->execute($query, $args);}
/** /**
* @param string $query * @param string $query
* @param array $args * @param array $args
* @return mixed|null|PDOStatement * @return PDOStatement
*/ */
public function get_row($query, $args=array()) {return $this->execute($query, $args);} public function get_row($query, $args=array()) {return $this->execute($query, $args);}
/** /**
* @param string $query * @param string $query
* @param array $args * @param array $args
* @return array|PDOStatement * @return PDOStatement
*/ */
public function get_col($query, $args=array()) {return $this->execute($query, $args);} public function get_col($query, $args=array()) {return $this->execute($query, $args);}
/** /**
* @param string $query * @param string $query
* @param array $args * @param array $args
* @return array|PDOStatement * @return PDOStatement
*/ */
public function get_pairs($query, $args=array()) {return $this->execute($query, $args);} public function get_pairs($query, $args=array()) {return $this->execute($query, $args);}
/** /**
* @param string $query * @param string $query
* @param array $args * @param array $args
* @return mixed|PDOStatement * @return PDOStatement
*/ */
public function get_one($query, $args=array()) {return $this->execute($query, $args);} public function get_one($query, $args=array()) {return $this->execute($query, $args);}

View File

@ -133,7 +133,7 @@ class PageRequestEvent extends Event {
public function get_search_terms() { public function get_search_terms() {
$search_terms = array(); $search_terms = array();
if($this->count_args() === 2) { if($this->count_args() === 2) {
$search_terms = explode(' ', $this->get_arg(0)); $search_terms = Tag::explode($this->get_arg(0));
} }
return $search_terms; return $search_terms;
} }

View File

@ -85,22 +85,16 @@ abstract class Extension {
/** @var array which DBs this ext supports (blank for 'all') */ /** @var array which DBs this ext supports (blank for 'all') */
protected $db_support = []; protected $db_support = [];
/** this theme's Themelet object */ /** @var Themelet this theme's Themelet object */
public $theme; public $theme;
/** @private */ public function __construct() {
var $_child; $this->theme = $this->get_theme_object(get_called_class());
// in PHP5.3, late static bindings can take care of this; __CLASS__
// used here will refer to the subclass
// http://php.net/manual/en/language.oop5.late-static-bindings.php
/** @private */
public function i_am(Extension $child) {
$this->_child = $child;
if(is_null($this->theme)) $this->theme = $this->get_theme_object($child, false);
} }
/**
* @return boolean
*/
public function is_live() { public function is_live() {
global $database; global $database;
return ( return (
@ -112,21 +106,21 @@ abstract class Extension {
/** /**
* Find the theme object for a given extension. * Find the theme object for a given extension.
* *
* @param Extension $class * @param string $base
* @param bool $fatal * @return Themelet
* @return bool
*/ */
private function get_theme_object(Extension $class, $fatal=true) { private function get_theme_object($base) {
$base = get_class($class); $custom = 'Custom'.$base.'Theme';
if(class_exists('Custom'.$base.'Theme')) { $normal = $base.'Theme';
$class = 'Custom'.$base.'Theme';
return new $class(); if(class_exists($custom)) {
return new $custom();
} }
elseif ($fatal || class_exists($base.'Theme')) { elseif(class_exists($normal)) {
$class = $base.'Theme'; return new $normal();
return new $class(); }
} else { else {
return false; return null;
} }
} }
@ -183,7 +177,7 @@ abstract class DataHandlerExtension extends Extension {
$supported_ext = $this->supported_ext($event->type); $supported_ext = $this->supported_ext($event->type);
$check_contents = $this->check_contents($event->tmpname); $check_contents = $this->check_contents($event->tmpname);
if($supported_ext && $check_contents) { if($supported_ext && $check_contents) {
if(!move_upload_to_archive($event)) return; move_upload_to_archive($event);
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
/* Check if we are replacing an image */ /* Check if we are replacing an image */

File diff suppressed because it is too large Load Diff

View File

@ -225,8 +225,9 @@ class Page {
*/ */
public function get_all_html_headers() { public function get_all_html_headers() {
$data = ''; $data = '';
ksort($this->html_headers);
foreach ($this->html_headers as $line) { foreach ($this->html_headers as $line) {
$data .= $line . "\n"; $data .= "\t\t" . $line . "\n";
} }
return $data; return $data;
} }
@ -289,7 +290,7 @@ class Page {
# header("Cache-control: no-cache"); # header("Cache-control: no-cache");
# header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT'); # header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
#} #}
if($this->get_cookie("flash_message")) { if($this->get_cookie("flash_message") !== null) {
$this->add_cookie("flash_message", "", -1, "/"); $this->add_cookie("flash_message", "", -1, "/");
} }
usort($this->blocks, "blockcmp"); usort($this->blocks, "blockcmp");
@ -337,18 +338,40 @@ class Page {
$this->add_html_header("<link rel='icon' type='image/x-icon' href='$data_href/favicon.ico'>", 41); $this->add_html_header("<link rel='icon' type='image/x-icon' href='$data_href/favicon.ico'>", 41);
$this->add_html_header("<link rel='apple-touch-icon' href='$data_href/apple-touch-icon.png'>", 42); $this->add_html_header("<link rel='apple-touch-icon' href='$data_href/apple-touch-icon.png'>", 42);
//We use $config_latest to make sure cache is reset if config is ever updated.
$config_latest = 0; $config_latest = 0;
foreach(zglob("data/config/*") as $conf) { foreach(zglob("data/config/*") as $conf) {
$config_latest = max($config_latest, filemtime($conf)); $config_latest = max($config_latest, filemtime($conf));
} }
$css_files = array(); /*** Generate CSS cache files ***/
$css_lib_latest = $config_latest;
$css_lib_files = zglob("lib/vendor/css/*.css");
foreach($css_lib_files as $css) {
$css_lib_latest = max($css_lib_latest, filemtime($css));
}
$css_lib_md5 = md5(serialize($css_lib_files));
$css_lib_cache_file = data_path("cache/style.lib.{$theme_name}.{$css_lib_latest}.{$css_lib_md5}.css");
if(!file_exists($css_lib_cache_file)) {
$css_lib_data = "";
foreach($css_lib_files as $file) {
$file_data = file_get_contents($file);
$pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/';
$replace = 'url("../../'.dirname($file).'/$1")';
$file_data = preg_replace($pattern, $replace, $file_data);
$css_lib_data .= $file_data . "\n";
}
file_put_contents($css_lib_cache_file, $css_lib_data);
}
$this->add_html_header("<link rel='stylesheet' href='$data_href/$css_lib_cache_file' type='text/css'>", 43);
$css_latest = $config_latest; $css_latest = $config_latest;
foreach(array_merge(zglob("lib/*.css"), zglob("ext/*/style.css"), zglob("themes/$theme_name/style.css")) as $css) { $css_files = array_merge(zglob("lib/shimmie.css"), zglob("ext/{".ENABLED_EXTS."}/style.css"), zglob("themes/$theme_name/style.css"));
$css_files[] = $css; foreach($css_files as $css) {
$css_latest = max($css_latest, filemtime($css)); $css_latest = max($css_latest, filemtime($css));
} }
$css_cache_file = data_path("cache/style.$theme_name.$css_latest.css"); $css_md5 = md5(serialize($css_files));
$css_cache_file = data_path("cache/style.main.{$theme_name}.{$css_latest}.{$css_md5}.css");
if(!file_exists($css_cache_file)) { if(!file_exists($css_cache_file)) {
$css_data = ""; $css_data = "";
foreach($css_files as $file) { foreach($css_files as $file) {
@ -360,15 +383,32 @@ class Page {
} }
file_put_contents($css_cache_file, $css_data); file_put_contents($css_cache_file, $css_data);
} }
$this->add_html_header("<link rel='stylesheet' href='$data_href/$css_cache_file' type='text/css'>", 43); $this->add_html_header("<link rel='stylesheet' href='$data_href/$css_cache_file' type='text/css'>", 100);
/*** Generate JS cache files ***/
$js_lib_latest = $config_latest;
$js_lib_files = zglob("lib/vendor/js/*.js");
foreach($js_lib_files as $js) {
$js_lib_latest = max($js_lib_latest, filemtime($js));
}
$js_lib_md5 = md5(serialize($js_lib_files));
$js_lib_cache_file = data_path("cache/script.lib.{$theme_name}.{$js_lib_latest}.{$js_lib_md5}.js");
if(!file_exists($js_lib_cache_file)) {
$js_data = "";
foreach($js_lib_files as $file) {
$js_data .= file_get_contents($file) . "\n";
}
file_put_contents($js_lib_cache_file, $js_data);
}
$this->add_html_header("<script src='$data_href/$js_lib_cache_file' type='text/javascript'></script>", 45);
$js_files = array();
$js_latest = $config_latest; $js_latest = $config_latest;
foreach(array_merge(zglob("lib/*.js"), zglob("ext/*/script.js"), zglob("themes/$theme_name/script.js")) as $js) { $js_files = array_merge(zglob("lib/shimmie.js"), zglob("ext/{".ENABLED_EXTS."}/script.js"), zglob("themes/$theme_name/script.js"));
$js_files[] = $js; foreach($js_files as $js) {
$js_latest = max($js_latest, filemtime($js)); $js_latest = max($js_latest, filemtime($js));
} }
$js_cache_file = data_path("cache/script.$theme_name.$js_latest.js"); $js_md5 = md5(serialize($js_files));
$js_cache_file = data_path("cache/script.main.{$theme_name}.{$js_latest}.{$js_md5}.js");
if(!file_exists($js_cache_file)) { if(!file_exists($js_cache_file)) {
$js_data = ""; $js_data = "";
foreach($js_files as $file) { foreach($js_files as $file) {
@ -376,7 +416,7 @@ class Page {
} }
file_put_contents($js_cache_file, $js_data); file_put_contents($js_cache_file, $js_data);
} }
$this->add_html_header("<script src='$data_href/$js_cache_file' type='text/javascript'></script>", 44); $this->add_html_header("<script src='$data_href/$js_cache_file' type='text/javascript'></script>", 100);
} }
} }

View File

@ -36,11 +36,12 @@ _d("COMPILE_ELS", false); // boolean pre-build the list of event listeners
_d("NICE_URLS", false); // boolean force niceurl mode _d("NICE_URLS", false); // boolean force niceurl mode
_d("SEARCH_ACCEL", false); // boolean use search accelerator _d("SEARCH_ACCEL", false); // boolean use search accelerator
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse _d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
_d("VERSION", '2.5.5'); // string shimmie version _d("VERSION", '2.6.1'); // string shimmie version
_d("TIMEZONE", null); // string timezone _d("TIMEZONE", null); // string timezone
_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable _d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable
_d("EXTRA_EXTS", ""); // optional extra extensions _d("EXTRA_EXTS", ""); // string optional extra extensions
_d("BASE_URL", null); // string force a specific base URL (default is auto-detect)
_d("MIN_PHP_VERSION", '5.6');// string minium supported PHP version
/* /*
* Calculated settings - you should never need to change these * Calculated settings - you should never need to change these

View File

@ -1,7 +1,10 @@
<?php <?php
require_once "lib/password.php";
/** @private */ /**
* @private
* @param mixed $row
* @return User
*/
function _new_user($row) { function _new_user($row) {
return new User($row); return new User($row);
} }
@ -137,19 +140,6 @@ class User {
} }
} }
/**
* @param int $offset
* @param int $limit
* @return array
*/
public static function by_list(/*int*/ $offset, /*int*/ $limit=50) {
assert('is_numeric($offset)', var_export($offset, true));
assert('is_numeric($limit)', var_export($limit, true));
global $database;
$rows = $database->get_all("SELECT * FROM users WHERE id >= :start AND id < :end", array("start"=>$offset, "end"=>$offset+$limit));
return array_map("_new_user", $rows);
}
/* useful user object functions start here */ /* useful user object functions start here */
@ -222,9 +212,15 @@ class User {
*/ */
public function set_password(/*string*/ $password) { public function set_password(/*string*/ $password) {
global $database; global $database;
$this->passhash = password_hash($password, PASSWORD_BCRYPT); $hash = password_hash($password, PASSWORD_BCRYPT);
$database->Execute("UPDATE users SET pass=:hash WHERE id=:id", array("hash"=>$this->passhash, "id"=>$this->id)); if(is_string($hash)) {
log_info("core-user", 'Set password for '.$this->name); $this->passhash = $hash;
$database->Execute("UPDATE users SET pass=:hash WHERE id=:id", array("hash"=>$this->passhash, "id"=>$this->id));
log_info("core-user", 'Set password for '.$this->name);
}
else {
throw new SCoreException("Failed to hash password");
}
} }
/** /**

View File

@ -1,6 +1,4 @@
<?php <?php
require_once "lib/recaptchalib.php";
require_once "lib/securimage/securimage.php";
require_once "lib/context.php"; require_once "lib/context.php";
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
@ -10,17 +8,27 @@ require_once "lib/context.php";
/** /**
* Make some data safe for printing into HTML * Make some data safe for printing into HTML
* *
* @param $input * @param string $input
* @return string * @return string
*/ */
function html_escape($input) { function html_escape($input) {
return htmlentities($input, ENT_QUOTES, "UTF-8"); return htmlentities($input, ENT_QUOTES, "UTF-8");
} }
/**
* Unescape data that was made safe for printing into HTML
*
* @param string $input
* @return string
*/
function html_unescape($input) {
return html_entity_decode($input, ENT_QUOTES, "UTF-8");
}
/** /**
* Make sure some data is safe to be used in integer context * Make sure some data is safe to be used in integer context
* *
* @param $input * @param string $input
* @return int * @return int
*/ */
function int_escape($input) { function int_escape($input) {
@ -34,7 +42,7 @@ function int_escape($input) {
/** /**
* Make sure some data is safe to be used in URL context * Make sure some data is safe to be used in URL context
* *
* @param $input * @param string $input
* @return string * @return string
*/ */
function url_escape($input) { function url_escape($input) {
@ -69,7 +77,7 @@ function url_escape($input) {
/** /**
* Make sure some data is safe to be used in SQL context * Make sure some data is safe to be used in SQL context
* *
* @param $input * @param string $input
* @return string * @return string
*/ */
function sql_escape($input) { function sql_escape($input) {
@ -81,8 +89,8 @@ function sql_escape($input) {
/** /**
* Turn all manner of HTML / INI / JS / DB booleans into a PHP one * Turn all manner of HTML / INI / JS / DB booleans into a PHP one
* *
* @param $input * @param mixed $input
* @return bool * @return boolean
*/ */
function bool_escape($input) { function bool_escape($input) {
/* /*
@ -117,7 +125,7 @@ function bool_escape($input) {
* Some functions require a callback function for escaping, * Some functions require a callback function for escaping,
* but we might not want to alter the data * but we might not want to alter the data
* *
* @param $input * @param string $input
* @return string * @return string
*/ */
function no_escape($input) { function no_escape($input) {
@ -168,8 +176,15 @@ function xml_tag($name, $attrs=array(), $children=array()) {
return $xml; return $xml;
} }
// Original PHP code by Chirp Internet: www.chirp.com.au /**
// Please acknowledge use of this code by including this header. * Original PHP code by Chirp Internet: www.chirp.com.au
* Please acknowledge use of this code by including this header.
*
* @param string $string input data
* @param int $limit how long the string should be
* @param string $break where to break the string
* @param string $pad what to add to the end of the string after truncating
*/
function truncate($string, $limit, $break=" ", $pad="...") { function truncate($string, $limit, $break=" ", $pad="...") {
// return with no change if string is shorter than $limit // return with no change if string is shorter than $limit
if(strlen($string) <= $limit) return $string; if(strlen($string) <= $limit) return $string;
@ -187,7 +202,7 @@ function truncate($string, $limit, $break=" ", $pad="...") {
/** /**
* Turn a human readable filesize into an integer, eg 1KB -> 1024 * Turn a human readable filesize into an integer, eg 1KB -> 1024
* *
* @param $limit * @param string|integer $limit
* @return int * @return int
*/ */
function parse_shorthand_int($limit) { function parse_shorthand_int($limit) {
@ -217,7 +232,7 @@ function parse_shorthand_int($limit) {
/** /**
* Turn an integer into a human readable filesize, eg 1024 -> 1KB * Turn an integer into a human readable filesize, eg 1024 -> 1KB
* *
* @param $int * @param integer $int
* @return string * @return string
*/ */
function to_shorthand_int($int) { function to_shorthand_int($int) {
@ -239,7 +254,7 @@ function to_shorthand_int($int) {
/** /**
* Turn a date into a time, a date, an "X minutes ago...", etc * Turn a date into a time, a date, an "X minutes ago...", etc
* *
* @param $date * @param string $date
* @param bool $html * @param bool $html
* @return string * @return string
*/ */
@ -252,7 +267,7 @@ function autodate($date, $html=true) {
/** /**
* Check if a given string is a valid date-time. ( Format: yyyy-mm-dd hh:mm:ss ) * Check if a given string is a valid date-time. ( Format: yyyy-mm-dd hh:mm:ss )
* *
* @param $dateTime * @param string $dateTime
* @return bool * @return bool
*/ */
function isValidDateTime($dateTime) { function isValidDateTime($dateTime) {
@ -268,7 +283,7 @@ function isValidDateTime($dateTime) {
/** /**
* Check if a given string is a valid date. ( Format: yyyy-mm-dd ) * Check if a given string is a valid date. ( Format: yyyy-mm-dd )
* *
* @param $date * @param string $date
* @return bool * @return bool
*/ */
function isValidDate($date) { function isValidDate($date) {
@ -282,6 +297,9 @@ function isValidDate($date) {
return false; return false;
} }
/**
* @param string[] $inputs
*/
function validate_input($inputs) { function validate_input($inputs) {
$outputs = array(); $outputs = array();
@ -376,8 +394,8 @@ function validate_input($inputs) {
* *
* FIXME: also check that IP ban ext is installed * FIXME: also check that IP ban ext is installed
* *
* @param $ip * @param string $ip
* @param $ban_reason * @param string $ban_reason
* @return string * @return string
*/ */
function show_ip($ip, $ban_reason) { function show_ip($ip, $ban_reason) {
@ -392,8 +410,8 @@ function show_ip($ip, $ban_reason) {
/** /**
* Checks if a given string contains another at the beginning. * Checks if a given string contains another at the beginning.
* *
* @param $haystack String to examine. * @param string $haystack String to examine.
* @param $needle String to look for. * @param string $needle String to look for.
* @return bool * @return bool
*/ */
function startsWith(/*string*/ $haystack, /*string*/ $needle) { function startsWith(/*string*/ $haystack, /*string*/ $needle) {
@ -404,8 +422,8 @@ function startsWith(/*string*/ $haystack, /*string*/ $needle) {
/** /**
* Checks if a given string contains another at the end. * Checks if a given string contains another at the end.
* *
* @param $haystack String to examine. * @param string $haystack String to examine.
* @param $needle String to look for. * @param string $needle String to look for.
* @return bool * @return bool
*/ */
function endsWith(/*string*/ $haystack, /*string*/ $needle) { function endsWith(/*string*/ $haystack, /*string*/ $needle) {
@ -414,7 +432,6 @@ function endsWith(/*string*/ $haystack, /*string*/ $needle) {
return (substr($haystack, $start) === $needle); return (substr($haystack, $start) === $needle);
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* HTML Generation * * HTML Generation *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@ -434,7 +451,10 @@ function make_link($page=null, $query=null) {
if(is_null($page)) $page = $config->get_string('main_page'); if(is_null($page)) $page = $config->get_string('main_page');
if(NICE_URLS || $config->get_bool('nice_urls', false)) { if(!is_null(BASE_URL)) {
$base = BASE_URL;
}
elseif(NICE_URLS || $config->get_bool('nice_urls', false)) {
$base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $_SERVER["PHP_SELF"]); $base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $_SERVER["PHP_SELF"]);
} }
else { else {
@ -535,7 +555,15 @@ function make_http(/*string*/ $link) {
*/ */
function make_form($target, $method="POST", $multipart=False, $form_id="", $onsubmit="") { function make_form($target, $method="POST", $multipart=False, $form_id="", $onsubmit="") {
global $user; global $user;
$auth = $user->get_auth_html(); if($method == "GET") {
$link = html_escape($target);
$target = make_link($target);
$extra_inputs = "<input type='hidden' name='q' value='$link'>";
}
else {
$extra_inputs = $user->get_auth_html();
}
$extra = empty($form_id) ? '' : 'id="'. $form_id .'"'; $extra = empty($form_id) ? '' : 'id="'. $form_id .'"';
if($multipart) { if($multipart) {
$extra .= " enctype='multipart/form-data'"; $extra .= " enctype='multipart/form-data'";
@ -543,7 +571,7 @@ function make_form($target, $method="POST", $multipart=False, $form_id="", $onsu
if($onsubmit) { if($onsubmit) {
$extra .= ' onsubmit="'.$onsubmit.'"'; $extra .= ' onsubmit="'.$onsubmit.'"';
} }
return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$auth; return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$extra_inputs;
} }
/** /**
@ -591,6 +619,32 @@ function zglob($pattern) {
} }
} }
/**
* Gets contact link as mailto: or http:
* @return string
*/
function contact_link() {
global $config;
$text = $config->get_string('contact_link');
if(
startsWith($text, "http:") ||
startsWith($text, "https:") ||
startsWith($text, "mailto:")
) {
return $text;
}
if(strpos($text, "@")) {
return "mailto:$text";
}
if(strpos($text, "/")) {
return "http://$text";
}
return $text;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* CAPTCHA abstraction * * CAPTCHA abstraction *
@ -608,14 +662,12 @@ function captcha_get_html() {
if($user->is_anonymous() && $config->get_bool("comment_captcha")) { if($user->is_anonymous() && $config->get_bool("comment_captcha")) {
$r_publickey = $config->get_string("api_recaptcha_pubkey"); $r_publickey = $config->get_string("api_recaptcha_pubkey");
if(!empty($r_publickey)) { if(!empty($r_publickey)) {
$captcha = recaptcha_get_html($r_publickey); $captcha = "
} <div class=\"g-recaptcha\" data-sitekey=\"{$r_publickey}\"></div>
else { <script type=\"text/javascript\" src=\"https://www.google.com/recaptcha/api.js\"></script>";
} else {
session_start(); session_start();
//$securimg = new Securimage(); $captcha = Securimage::getCaptchaHtml(['securimage_path' => './vendor/dapphp/securimage/']);
$base = get_base_href();
$captcha = "<br/><img src='$base/lib/securimage/securimage_show.php?sid=". md5(uniqid(time())) ."'>".
"<br/>CAPTCHA: <input type='text' name='code' value='' />";
} }
} }
return $captcha; return $captcha;
@ -632,22 +684,18 @@ function captcha_check() {
if($user->is_anonymous() && $config->get_bool("comment_captcha")) { if($user->is_anonymous() && $config->get_bool("comment_captcha")) {
$r_privatekey = $config->get_string('api_recaptcha_privkey'); $r_privatekey = $config->get_string('api_recaptcha_privkey');
if(!empty($r_privatekey)) { if(!empty($r_privatekey)) {
$resp = recaptcha_check_answer( $recaptcha = new \ReCaptcha\ReCaptcha($r_privatekey);
$r_privatekey, $resp = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
$_SERVER["REMOTE_ADDR"],
$_POST["recaptcha_challenge_field"],
$_POST["recaptcha_response_field"]
);
if(!$resp->is_valid) { if(!$resp->isSuccess()) {
log_info("core", "Captcha failed (ReCaptcha): " . $resp->error); log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes()));
return false; return false;
} }
} }
else { else {
session_start(); session_start();
$securimg = new Securimage(); $securimg = new Securimage();
if($securimg->check($_POST['code']) == false) { if($securimg->check($_POST['captcha_code']) === false) {
log_info("core", "Captcha failed (Securimage)"); log_info("core", "Captcha failed (Securimage)");
return false; return false;
} }
@ -678,7 +726,7 @@ function is_https_enabled() {
* from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Schönknecht * from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Schönknecht
* and released under the 'Simplified BSD License'. * and released under the 'Simplified BSD License'.
* *
* @param string &$file File path * @param string $file File path
* @param string $ext * @param string $ext
* @param bool $list * @param bool $list
* @return string * @return string
@ -704,7 +752,7 @@ function getMimeType($file, $ext="", $list=false) {
'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm' 'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm'
); );
if ($list == true){ return $exts; } if ($list === true){ return $exts; }
if (isset($exts[$ext])) { return $exts[$ext]; } if (isset($exts[$ext])) { return $exts[$ext]; }
@ -799,7 +847,7 @@ function get_memory_limit() {
// Shimmie wants more memory than what PHP is currently set for. // Shimmie wants more memory than what PHP is currently set for.
// Attempt to set PHP's memory limit. // Attempt to set PHP's memory limit.
if ( ini_set("memory_limit", $shimmie_limit) === FALSE ) { if ( ini_set("memory_limit", $shimmie_limit) === false ) {
/* We can't change PHP's limit, oh well, return whatever its currently set to */ /* We can't change PHP's limit, oh well, return whatever its currently set to */
return $memory; return $memory;
} }
@ -918,6 +966,17 @@ function data_path($filename) {
return $filename; return $filename;
} }
if (!function_exists('mb_strlen')) {
// TODO: we should warn the admin that they are missing multibyte support
function mb_strlen($str, $encoding) {
return strlen($str);
}
function mb_internal_encoding($encoding) {}
function mb_strtolower($str) {
return strtolower($str);
}
}
/** /**
* @param string $url * @param string $url
* @param string $mfile * @param string $mfile
@ -982,6 +1041,11 @@ function transload($url, $mfile) {
} }
if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/function.http-parse-headers.php#112917 if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/function.http-parse-headers.php#112917
/**
* @param string $raw_headers
* @return string[]
*/
function http_parse_headers ($raw_headers){ function http_parse_headers ($raw_headers){
$headers = array(); // $headers = []; $headers = array(); // $headers = [];
@ -1009,8 +1073,8 @@ if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/func
* In cases like these, we need to make sure to check for them if the camelcase version does not exist. * In cases like these, we need to make sure to check for them if the camelcase version does not exist.
* *
* @param array $headers * @param array $headers
* @param mixed $name * @param string $name
* @return mixed * @return string|bool
*/ */
function findHeader ($headers, $name) { function findHeader ($headers, $name) {
if (!is_array($headers)) { if (!is_array($headers)) {
@ -1111,10 +1175,40 @@ function log_msg(/*string*/ $section, /*int*/ $priority, /*string*/ $message, $f
} }
// More shorthand ways of logging // More shorthand ways of logging
/**
* @param string $section
* @param string $message
* @param bool|string $flash
* @param array $args
*/
function log_debug( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_DEBUG, $message, $flash, $args);} function log_debug( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_DEBUG, $message, $flash, $args);}
/**
* @param string $section
* @param string $message
* @param bool|string $flash
* @param array $args
*/
function log_info( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_INFO, $message, $flash, $args);} function log_info( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_INFO, $message, $flash, $args);}
/**
* @param string $section
* @param string $message
* @param bool|string $flash
* @param array $args
*/
function log_warning( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_WARNING, $message, $flash, $args);} function log_warning( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_WARNING, $message, $flash, $args);}
/**
* @param string $section
* @param string $message
* @param bool|string $flash
* @param array $args
*/
function log_error( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_ERROR, $message, $flash, $args);} function log_error( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_ERROR, $message, $flash, $args);}
/**
* @param string $section
* @param string $message
* @param bool|string $flash
* @param array $args
*/
function log_critical(/*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_CRITICAL, $message, $flash, $args);} function log_critical(/*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_CRITICAL, $message, $flash, $args);}
@ -1145,8 +1239,8 @@ function get_request_id() {
/** /**
* Remove an item from an array * Remove an item from an array
* *
* @param $array * @param array $array
* @param $to_remove * @param mixed $to_remove
* @return array * @return array
*/ */
function array_remove($array, $to_remove) { function array_remove($array, $to_remove) {
@ -1165,8 +1259,8 @@ function array_remove($array, $to_remove) {
* *
* Also removes duplicate values from the array. * Also removes duplicate values from the array.
* *
* @param $array * @param array $array
* @param $element * @param mixed $element
* @return array * @return array
*/ */
function array_add($array, $element) { function array_add($array, $element) {
@ -1180,7 +1274,7 @@ function array_add($array, $element) {
/** /**
* Return the unique elements of an array, case insensitively * Return the unique elements of an array, case insensitively
* *
* @param $array * @param array $array
* @return array * @return array
*/ */
function array_iunique($array) { function array_iunique($array) {
@ -1204,8 +1298,8 @@ function array_iunique($array) {
* *
* from http://uk.php.net/network * from http://uk.php.net/network
* *
* @param $IP * @param string $IP
* @param $CIDR * @param string $CIDR
* @return bool * @return bool
*/ */
function ip_in_range($IP, $CIDR) { function ip_in_range($IP, $CIDR) {
@ -1344,7 +1438,10 @@ function list_files(/*string*/ $base, $_sub_dir="") {
return $file_list; return $file_list;
} }
/**
* @param string $path
* @return string
*/
function path_to_tags($path) { function path_to_tags($path) {
$matches = array(); $matches = array();
if(preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", basename($path), $matches)) { if(preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", basename($path), $matches)) {
@ -1400,7 +1497,6 @@ function _set_event_listeners() {
elseif(is_subclass_of($class, "Extension")) { elseif(is_subclass_of($class, "Extension")) {
/** @var Extension $extension */ /** @var Extension $extension */
$extension = new $class(); $extension = new $class();
$extension->i_am($extension);
// skip extensions which don't support our current database // skip extensions which don't support our current database
if(!$extension->is_live()) continue; if(!$extension->is_live()) continue;
@ -1419,6 +1515,10 @@ function _set_event_listeners() {
} }
} }
/**
* @param array $event_listeners
* @param string $path
*/
function _dump_event_listeners($event_listeners, $path) { function _dump_event_listeners($event_listeners, $path) {
$p = "<"."?php\n"; $p = "<"."?php\n";
@ -1427,7 +1527,6 @@ function _dump_event_listeners($event_listeners, $path) {
if($rclass->isAbstract()) {} if($rclass->isAbstract()) {}
elseif(is_subclass_of($class, "Extension")) { elseif(is_subclass_of($class, "Extension")) {
$p .= "\$$class = new $class(); "; $p .= "\$$class = new $class(); ";
$p .= "\${$class}->i_am(\$$class);\n";
} }
} }
@ -1446,7 +1545,7 @@ function _dump_event_listeners($event_listeners, $path) {
} }
/** /**
* @param $ext_name string * @param string $ext_name Main class name (eg ImageIO as opposed to ImageIOTheme or ImageIOTest)
* @return bool * @return bool
*/ */
function ext_is_live($ext_name) { function ext_is_live($ext_name) {
@ -1552,14 +1651,17 @@ function score_assert_handler($file, $line, $code, $desc = null) {
/** @privatesection */ /** @privatesection */
function _version_check() { function _version_check() {
$min_version = "5.4.8"; if(MIN_PHP_VERSION)
if(version_compare(PHP_VERSION, $min_version) == -1) { {
print " if(version_compare(phpversion(), MIN_PHP_VERSION, ">=") === FALSE) {
Currently SCore Engine doesn't support versions of PHP lower than $min_version -- print "
if your web host is running an older version, they are dangerously out of Shimmie (SCore Engine) does not support versions of PHP lower than ".MIN_PHP_VERSION."
(PHP reports that it is version ".phpversion().")
If your web host is running an older version, they are dangerously out of
date and you should plan on moving elsewhere. date and you should plan on moving elsewhere.
"; ";
exit; exit;
}
} }
} }
@ -1690,6 +1792,9 @@ function _get_user() {
return $user; return $user;
} }
/**
* @return string
*/
function _get_query() { function _get_query() {
return @$_POST["q"]?:@$_GET["q"]; return @$_POST["q"]?:@$_GET["q"];
} }

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
* Documentation: * Documentation:
*/ */
require_once "lib/S3.php"; require_once "ext/amazon_s3/lib/S3.php";
class UploadS3 extends Extension { class UploadS3 extends Extension {
public function onInitExt(InitExtEvent $event) { public function onInitExt(InitExtEvent $event) {

View File

@ -47,6 +47,7 @@ class ArrowkeyNavigation extends Extension {
(function($){ (function($){
$(document).keyup(function(e) { $(document).keyup(function(e) {
if($(e.target).is('input', 'textarea')){ return; } if($(e.target).is('input', 'textarea')){ return; }
if (e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) { return; }
if (e.keyCode == 37) { window.location.href = '{$prev_url}'; } if (e.keyCode == 37) { window.location.href = '{$prev_url}'; }
else if (e.keyCode == 39) { window.location.href = '{$next_url}'; } else if (e.keyCode == 39) { window.location.href = '{$next_url}'; }
}); });

View File

@ -837,7 +837,7 @@ class Artists extends Extension {
INSERT INTO artists (user_id, name, notes, created, updated) INSERT INTO artists (user_id, name, notes, created, updated)
VALUES (?, ?, ?, now(), now()) VALUES (?, ?, ?, now(), now())
", array($user->id, $name, $notes)); ", array($user->id, $name, $notes));
return $database->get_last_insert_id(); return $database->get_last_insert_id('artists_id_seq');
} }
/** /**

File diff suppressed because one or more lines are too long

13
ext/autocomplete/lib/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,69 @@
ul.tagit {
padding: 1px 5px;
overflow: auto;
margin-left: inherit; /* usually we don't want the regular ul margins. */
margin-right: inherit;
}
ul.tagit li {
display: block;
float: left;
margin: 2px 5px 2px 0;
}
ul.tagit li.tagit-choice {
position: relative;
line-height: inherit;
}
input.tagit-hidden-field {
display: none;
}
ul.tagit li.tagit-choice-read-only {
padding: .2em .5em .2em .5em;
}
ul.tagit li.tagit-choice-editable {
padding: .2em 18px .2em .5em;
}
ul.tagit li.tagit-new {
padding: .25em 4px .25em 0;
}
ul.tagit li.tagit-choice a.tagit-label {
cursor: pointer;
text-decoration: none;
}
ul.tagit li.tagit-choice .tagit-close {
cursor: pointer;
position: absolute;
right: .1em;
top: 50%;
margin-top: -8px;
line-height: 17px;
}
/* used for some custom themes that don't need image icons */
ul.tagit li.tagit-choice .tagit-close .text-icon {
display: none;
}
ul.tagit li.tagit-choice input {
display: block;
float: left;
margin: 2px 5px 2px 0;
}
ul.tagit input[type="text"] {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
border: none;
margin: 0;
padding: 0;
width: inherit;
background-color: inherit;
outline: none;
}

18
ext/autocomplete/lib/tag-it.min.js vendored Normal file
View File

@ -0,0 +1,18 @@
//Removed TAB keybind
;(function(b){b.widget("ui.tagit",{options:{allowDuplicates:!1,caseSensitive:!0,fieldName:"tags",placeholderText:null,readOnly:!1,removeConfirmation:!1,tagLimit:null,availableTags:[],autocomplete:{},showAutocompleteOnFocus:!1,allowSpaces:!1,singleField:!1,singleFieldDelimiter:",",singleFieldNode:null,animate:!0,tabIndex:null,beforeTagAdded:null,afterTagAdded:null,beforeTagRemoved:null,afterTagRemoved:null,onTagClicked:null,onTagLimitExceeded:null,onTagAdded:null,onTagRemoved:null,tagSource:null},_create:function(){var a=
this;this.element.is("input")?(this.tagList=b("<ul></ul>").insertAfter(this.element),this.options.singleField=!0,this.options.singleFieldNode=this.element,this.element.addClass("tagit-hidden-field")):this.tagList=this.element.find("ul, ol").andSelf().last();this.tagInput=b('<input type="text" />').addClass("ui-widget-content");this.options.readOnly&&this.tagInput.attr("disabled","disabled");this.options.tabIndex&&this.tagInput.attr("tabindex",this.options.tabIndex);this.options.placeholderText&&this.tagInput.attr("placeholder",
this.options.placeholderText);this.options.autocomplete.source||(this.options.autocomplete.source=function(a,e){var d=a.term.toLowerCase(),c=b.grep(this.options.availableTags,function(a){return 0===a.toLowerCase().indexOf(d)});this.options.allowDuplicates||(c=this._subtractArray(c,this.assignedTags()));e(c)});this.options.showAutocompleteOnFocus&&(this.tagInput.focus(function(b,d){a._showAutocomplete()}),"undefined"===typeof this.options.autocomplete.minLength&&(this.options.autocomplete.minLength=
0));b.isFunction(this.options.autocomplete.source)&&(this.options.autocomplete.source=b.proxy(this.options.autocomplete.source,this));b.isFunction(this.options.tagSource)&&(this.options.tagSource=b.proxy(this.options.tagSource,this));this.tagList.addClass("tagit").addClass("ui-widget ui-widget-content ui-corner-all").append(b('<li class="tagit-new"></li>').append(this.tagInput)).click(function(d){var c=b(d.target);c.hasClass("tagit-label")?(c=c.closest(".tagit-choice"),c.hasClass("removed")||a._trigger("onTagClicked",
d,{tag:c,tagLabel:a.tagLabel(c)})):a.tagInput.focus()});var c=!1;if(this.options.singleField)if(this.options.singleFieldNode){var d=b(this.options.singleFieldNode),f=d.val().split(this.options.singleFieldDelimiter);d.val("");b.each(f,function(b,d){a.createTag(d,null,!0);c=!0})}else this.options.singleFieldNode=b('<input type="hidden" style="display:none;" value="" name="'+this.options.fieldName+'" />'),this.tagList.after(this.options.singleFieldNode);c||this.tagList.children("li").each(function(){b(this).hasClass("tagit-new")||
(a.createTag(b(this).text(),b(this).attr("class"),!0),b(this).remove())});this.tagInput.keydown(function(c){if(c.which==b.ui.keyCode.BACKSPACE&&""===a.tagInput.val()){var d=a._lastTag();!a.options.removeConfirmation||d.hasClass("remove")?a.removeTag(d):a.options.removeConfirmation&&d.addClass("remove ui-state-highlight")}else a.options.removeConfirmation&&a._lastTag().removeClass("remove ui-state-highlight");if(c.which===b.ui.keyCode.COMMA&&!1===c.shiftKey||c.which===b.ui.keyCode.ENTER||c.which==
c.which==b.ui.keyCode.SPACE&&!0!==a.options.allowSpaces&&('"'!=b.trim(a.tagInput.val()).replace(/^s*/,"").charAt(0)||'"'==b.trim(a.tagInput.val()).charAt(0)&&'"'==b.trim(a.tagInput.val()).charAt(b.trim(a.tagInput.val()).length-1)&&0!==b.trim(a.tagInput.val()).length-1))c.which===b.ui.keyCode.ENTER&&""===a.tagInput.val()||c.preventDefault(),a.options.autocomplete.autoFocus&&a.tagInput.data("autocomplete-open")||(a.tagInput.autocomplete("close"),a.createTag(a._cleanedInput()))}).blur(function(b){a.tagInput.data("autocomplete-open")||
a.createTag(a._cleanedInput())});if(this.options.availableTags||this.options.tagSource||this.options.autocomplete.source)d={select:function(b,c){a.createTag(c.item.value);return!1}},b.extend(d,this.options.autocomplete),d.source=this.options.tagSource||d.source,this.tagInput.autocomplete(d).bind("autocompleteopen.tagit",function(b,c){a.tagInput.data("autocomplete-open",!0)}).bind("autocompleteclose.tagit",function(b,c){a.tagInput.data("autocomplete-open",!1)}),this.tagInput.autocomplete("widget").addClass("tagit-autocomplete")},
destroy:function(){b.Widget.prototype.destroy.call(this);this.element.unbind(".tagit");this.tagList.unbind(".tagit");this.tagInput.removeData("autocomplete-open");this.tagList.removeClass("tagit ui-widget ui-widget-content ui-corner-all tagit-hidden-field");this.element.is("input")?(this.element.removeClass("tagit-hidden-field"),this.tagList.remove()):(this.element.children("li").each(function(){b(this).hasClass("tagit-new")?b(this).remove():(b(this).removeClass("tagit-choice ui-widget-content ui-state-default ui-state-highlight ui-corner-all remove tagit-choice-editable tagit-choice-read-only"),
b(this).text(b(this).children(".tagit-label").text()))}),this.singleFieldNode&&this.singleFieldNode.remove());return this},_cleanedInput:function(){return b.trim(this.tagInput.val().replace(/^"(.*)"$/,"$1"))},_lastTag:function(){return this.tagList.find(".tagit-choice:last:not(.removed)")},_tags:function(){return this.tagList.find(".tagit-choice:not(.removed)")},assignedTags:function(){var a=this,c=[];this.options.singleField?(c=b(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter),
""===c[0]&&(c=[])):this._tags().each(function(){c.push(a.tagLabel(this))});return c},_updateSingleTagsField:function(a){b(this.options.singleFieldNode).val(a.join(this.options.singleFieldDelimiter)).trigger("change")},_subtractArray:function(a,c){for(var d=[],f=0;f<a.length;f++)-1==b.inArray(a[f],c)&&d.push(a[f]);return d},tagLabel:function(a){return this.options.singleField?b(a).find(".tagit-label:first").text():b(a).find("input:first").val()},_showAutocomplete:function(){this.tagInput.autocomplete("search",
"")},_findTagByLabel:function(a){var c=this,d=null;this._tags().each(function(f){if(c._formatStr(a)==c._formatStr(c.tagLabel(this)))return d=b(this),!1});return d},_isNew:function(a){return!this._findTagByLabel(a)},_formatStr:function(a){return this.options.caseSensitive?a:b.trim(a.toLowerCase())},_effectExists:function(a){return Boolean(b.effects&&(b.effects[a]||b.effects.effect&&b.effects.effect[a]))},createTag:function(a,c,d){var f=this;a=b.trim(a);this.options.preprocessTag&&(a=this.options.preprocessTag(a));
if(""===a)return!1;if(!this.options.allowDuplicates&&!this._isNew(a))return a=this._findTagByLabel(a),!1!==this._trigger("onTagExists",null,{existingTag:a,duringInitialization:d})&&this._effectExists("highlight")&&a.effect("highlight"),!1;if(this.options.tagLimit&&this._tags().length>=this.options.tagLimit)return this._trigger("onTagLimitExceeded",null,{duringInitialization:d}),!1;var g=b(this.options.onTagClicked?'<a class="tagit-label"></a>':'<span class="tagit-label"></span>').text(a),e=b("<li></li>").addClass("tagit-choice ui-widget-content ui-state-default ui-corner-all").addClass(c).append(g);
this.options.readOnly?e.addClass("tagit-choice-read-only"):(e.addClass("tagit-choice-editable"),c=b("<span></span>").addClass("ui-icon ui-icon-close"),c=b('<a><span class="text-icon">\u00d7</span></a>').addClass("tagit-close").append(c).click(function(a){f.removeTag(e)}),e.append(c));this.options.singleField||(g=g.html(),e.append('<input type="hidden" value="'+g+'" name="'+this.options.fieldName+'" class="tagit-hidden-field" />'));!1!==this._trigger("beforeTagAdded",null,{tag:e,tagLabel:this.tagLabel(e),
duringInitialization:d})&&(this.options.singleField&&(g=this.assignedTags(),g.push(a),this._updateSingleTagsField(g)),this._trigger("onTagAdded",null,e),this.tagInput.val(""),this.tagInput.parent().before(e),this._trigger("afterTagAdded",null,{tag:e,tagLabel:this.tagLabel(e),duringInitialization:d}),this.options.showAutocompleteOnFocus&&!d&&setTimeout(function(){f._showAutocomplete()},0))},removeTag:function(a,c){c="undefined"===typeof c?this.options.animate:c;a=b(a);this._trigger("onTagRemoved",
null,a);if(!1!==this._trigger("beforeTagRemoved",null,{tag:a,tagLabel:this.tagLabel(a)})){if(this.options.singleField){var d=this.assignedTags(),f=this.tagLabel(a),d=b.grep(d,function(a){return a!=f});this._updateSingleTagsField(d)}if(c){a.addClass("removed");var d=this._effectExists("blind")?["blind",{direction:"horizontal"},"fast"]:["fast"],g=this;d.push(function(){a.remove();g._trigger("afterTagRemoved",null,{tag:a,tagLabel:g.tagLabel(a)})});a.fadeOut("fast").hide.apply(a,d).dequeue()}else a.remove(),
this._trigger("afterTagRemoved",null,{tag:a,tagLabel:this.tagLabel(a)})}},removeTagByLabel:function(a,b){var d=this._findTagByLabel(a);if(!d)throw"No such tag exists with the name '"+a+"'";this.removeTag(d,b)},removeAll:function(){var a=this;this._tags().each(function(b,d){a.removeTag(d,!1)})}})})(jQuery);

View File

@ -0,0 +1,98 @@
/* Optional scoped theme for tag-it which mimics the zendesk widget. */
ul.tagit {
border-style: solid;
border-width: 1px;
border-color: #C6C6C6;
background: inherit;
}
ul.tagit li.tagit-choice {
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-border-radius: 6px;
border: 1px solid #CAD8F3;
background: none;
background-color: #DEE7F8;
font-weight: normal;
}
ul.tagit li.tagit-choice .tagit-label:not(a) {
color: #555;
}
ul.tagit li.tagit-choice a.tagit-close {
text-decoration: none;
}
ul.tagit li.tagit-choice .tagit-close {
right: .4em;
}
ul.tagit li.tagit-choice .ui-icon {
display: none;
}
ul.tagit li.tagit-choice .tagit-close .text-icon {
display: inline;
font-family: arial, sans-serif;
font-size: 16px;
line-height: 16px;
color: #777;
}
ul.tagit li.tagit-choice:hover, ul.tagit li.tagit-choice.remove {
background-color: #bbcef1;
border-color: #6d95e0;
}
ul.tagit li.tagit-choice a.tagLabel:hover,
ul.tagit li.tagit-choice a.tagit-close .text-icon:hover {
color: #222;
}
ul.tagit input[type="text"] {
color: #333333;
background: none;
}
.ui-widget {
font-size: 1.1em;
}
/* Forked from a jQuery UI theme, so that we don't require the jQuery UI CSS as a dependency. */
.tagit-autocomplete.ui-autocomplete { position: absolute; cursor: default; }
* html .tagit-autocomplete.ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
.tagit-autocomplete.ui-menu {
list-style:none;
padding: 2px;
margin: 0;
display:block;
float: left;
}
.tagit-autocomplete.ui-menu .ui-menu {
margin-top: -3px;
}
.tagit-autocomplete.ui-menu .ui-menu-item {
margin:0;
padding: 0;
zoom: 1;
float: left;
clear: left;
width: 100%;
}
.tagit-autocomplete.ui-menu .ui-menu-item a {
text-decoration:none;
display:block;
padding:.2em .4em;
line-height:1.5;
zoom:1;
}
.tagit-autocomplete .ui-menu .ui-menu-item a.ui-state-hover,
.tagit-autocomplete .ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: -1px;
}
.tagit-autocomplete.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff 50% 50% repeat-x; color: #222222; }
.tagit-autocomplete.ui-corner-all, .tagit-autocomplete .ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; -khtml-border-radius: 4px; border-radius: 4px; }
.tagit-autocomplete .ui-state-hover, .tagit-autocomplete .ui-state-focus { border: 1px solid #999999; background: #dadada; font-weight: normal; color: #212121; }
.tagit-autocomplete .ui-state-active { border: 1px solid #aaaaaa; }
.tagit-autocomplete .ui-widget-content { border: 1px solid #aaaaaa; }
.tagit .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px,1px,1px,1px); }

47
ext/autocomplete/main.php Normal file
View File

@ -0,0 +1,47 @@
<?php
/*
* Name: Autocomplete
* Author: Daku <admin@codeanimu.net>
* Description: Adds autocomplete to search & tagging.
*/
class AutoComplete extends Extension {
public function get_priority() {return 30;} // before Home
public function onPageRequest(PageRequestEvent $event) {
global $page, $database;
if($event->page_matches("api/internal/autocomplete")) {
if(!isset($_GET["s"])) return;
//$limit = 0;
$cache_key = "autocomplete-" . strtolower($_GET["s"]);
$limitSQL = "";
$SQLarr = array("search"=>$_GET["s"]."%");
if(isset($_GET["limit"]) && $_GET["limit"] !== 0){
$limitSQL = "LIMIT :limit";
$SQLarr['limit'] = $_GET["limit"];
$cache_key .= "-" . $_GET["limit"];
}
$res = $database->cache->get($cache_key);
if(!$res) {
$res = $database->get_pairs($database->scoreql_to_sql("
SELECT tag, count
FROM tags
WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:search)
AND count > 0
ORDER BY count DESC
$limitSQL"), $SQLarr
);
$database->cache->set($cache_key, $res, 600);
}
$page->set_mode("data");
$page->set_type("application/json");
$page->set_data(json_encode($res));
}
$this->theme->build_autocomplete($page);
}
}

View File

@ -0,0 +1,81 @@
$(function(){
var metatags = ['order:id', 'order:width', 'order:height', 'order:filesize', 'order:filename'];
$('[name=search]').tagit({
singleFieldDelimiter: ' ',
beforeTagAdded: function(event, ui) {
if(metatags.indexOf(ui.tagLabel) !== -1) {
ui.tag.addClass('tag-metatag');
} else {
console.log(ui.tagLabel);
// give special class to negative tags
if(ui.tagLabel[0] === '-') {
ui.tag.addClass('tag-negative');
}else{
ui.tag.addClass('tag-positive');
}
}
},
autocomplete : ({
source: function (request, response) {
var ac_metatags = $.map(
$.grep(metatags, function(s) {
// Only show metatags for strings longer than one character
return (request.term.length > 1 && s.indexOf(request.term) === 0);
}),
function(item) {
return {
label : item + ' [metatag]',
value : item
};
}
);
var isNegative = (request.term[0] === '-');
$.ajax({
url: base_href + '/api/internal/autocomplete',
data: {'s': (isNegative ? request.term.substring(1) : request.term)},
dataType : 'json',
type : 'GET',
success : function (data) {
response(
$.merge(ac_metatags,
$.map(data, function (count, item) {
item = (isNegative ? '-'+item : item);
return {
label : item + ' ('+count+')',
value : item
};
})
)
);
},
error : function (request, status, error) {
alert(error);
}
});
},
minLength: 1
})
});
$('.ui-autocomplete-input').keydown(function(e) {
var keyCode = e.keyCode || e.which;
//Stop tags containing space.
if(keyCode == 32) {
e.preventDefault();
$('[name=search]').tagit('createTag', $(this).val());
$(this).autocomplete('close');
} else if (keyCode == 9) {
e.preventDefault();
var tag = $('.tagit-autocomplete[style*=\"display: block\"] > li:focus, .tagit-autocomplete[style*=\"display: block\"] > li:first').first();
if(tag.length){
$(tag).click();
$('.ui-autocomplete-input').val(''); //If tag already exists, make sure to remove duplicate.
}
}
});
});

View File

@ -0,0 +1,9 @@
#Navigationleft .blockbody { overflow: visible; }
.tagit { background: white !important; border: 1px solid grey !important; cursor: text; }
.tagit-choice { cursor: initial; }
input[name=search] ~ input[type=submit] { display: inline-block !important; }
.tag-negative { background: #ff8080 !important; }
.tag-positive { background: #40bf40 !important; }
.tag-metatag { background: #eaa338 !important; }

View File

@ -0,0 +1,13 @@
<?php
class AutoCompleteTheme extends Themelet {
public function build_autocomplete(Page $page) {
$base_href = get_base_href();
// TODO: AJAX test and fallback.
$page->add_html_header("<script src='$base_href/ext/autocomplete/lib/jquery-ui.min.js' type='text/javascript'></script>");
$page->add_html_header("<script src='$base_href/ext/autocomplete/lib/tag-it.min.js' type='text/javascript'></script>");
$page->add_html_header('<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/flick/jquery-ui.css">');
$page->add_html_header("<link rel='stylesheet' type='text/css' href='$base_href/ext/autocomplete/lib/jquery.tagit.css' />");
}
}

View File

@ -86,6 +86,12 @@ xanax
$event->panel->add_block($sb); $event->panel->add_block($sb);
} }
/**
* Throws if the comment contains banned words.
* @param string $comment
* @param CommentPostingException|SCoreException $ex
* @throws CommentPostingException|SCoreException if the comment contains banned words.
*/
private function test_text($comment, $ex) { private function test_text($comment, $ex) {
$comment = strtolower($comment); $comment = strtolower($comment);
@ -105,6 +111,9 @@ xanax
} }
} }
/**
* @return string[]
*/
private function get_words() { private function get_words() {
global $config; global $config;
$words = array(); $words = array();

View File

@ -40,8 +40,11 @@ class Blocks extends Extension {
$database->cache->set("blocks", $blocks, 600); $database->cache->set("blocks", $blocks, 600);
} }
foreach($blocks as $block) { foreach($blocks as $block) {
if(fnmatch($block['pages'], implode("/", $event->args))) { $path = implode("/", $event->args);
$page->add_block(new Block($block['title'], $block['content'], $block['area'], $block['priority'])); if(strlen($path) < 4000 && fnmatch($block['pages'], $path)) {
$b = new Block($block['title'], $block['content'], $block['area'], $block['priority']);
$b->is_content = false;
$page->add_block($b);
} }
} }

View File

@ -4,14 +4,14 @@ $(document).ready(function() {
$(".shm-blotter2-toggle").click(function() { $(".shm-blotter2-toggle").click(function() {
$(".shm-blotter2").slideToggle("slow", function() { $(".shm-blotter2").slideToggle("slow", function() {
if($(".shm-blotter2").is(":hidden")) { if($(".shm-blotter2").is(":hidden")) {
$.cookie("ui-blotter2-hidden", 'true', {path: '/'}); Cookies.set("ui-blotter2-hidden", 'true');
} }
else { else {
$.cookie("ui-blotter2-hidden", 'false', {path: '/'}); Cookies.set("ui-blotter2-hidden", 'false');
} }
}); });
}); });
if($.cookie("ui-blotter2-hidden") === 'true') { if(Cookies.get("ui-blotter2-hidden") === 'true') {
$(".shm-blotter2").hide(); $(".shm-blotter2").hide();
} }
}); });

View File

@ -176,4 +176,3 @@ class BlotterTheme extends Themelet {
return $html; return $html;
} }
} }

View File

@ -1,68 +0,0 @@
<?php
/**
* Name: Bookmarks
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Allow users to bookmark searches
*/
class Bookmarks extends Extension {
public function onInitExt(InitExtEvent $event) {
$this->install();
}
public function onPageRequest(PageRequestEvent $event) {
global $page;
if($event->page_matches("bookmark")) {
if($event->get_arg(0) == "add") {
if(isset($_POST['url'])) {
$page->set_mode("redirect");
$page->set_redirect(make_link("user"));
}
}
else if($event->get_arg(0) == "remove") {
if(isset($_POST['id'])) {
$page->set_mode("redirect");
$page->set_redirect(make_link("user"));
}
}
}
}
protected function install() {
global $database, $config;
// shortcut to latest
if($config->get_int("ext_bookmarks_version") < 1) {
$database->create_table("bookmark", "
id SCORE_AIPK,
owner_id INTEGER NOT NULL,
url TEXT NOT NULL,
title TEXT NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE
");
$database->execute("CREATE INDEX bookmark_owner_id_idx ON bookmark(owner_id)", array());
$config->set_int("ext_bookmarks_version", 1);
}
}
private function get_bookmarks() {
global $database;
$bms = $database->get_all("
SELECT *
FROM bookmark
WHERE bookmark.owner_id = ?
");
if($bms) {return $bms;}
else {return array();}
}
private function add_bookmark(/*string*/ $url, /*string*/ $title) {
global $database, $user;
$sql = "INSERT INTO bookmark(owner_id, url, title) VALUES (?, ?, ?)";
$database->Execute($sql, array($user->id, $url, $title));
}
}

View File

@ -1,8 +0,0 @@
<?php
class BookmarksTest extends ShimmiePHPUnitTestCase {
public function testBookmarks() {
$this->get_page("bookmark/add");
$this->get_page("bookmark/remove");
}
}

View File

@ -1,5 +0,0 @@
<?php
class BookmarksTheme extends Themelet {
}

View File

@ -32,7 +32,11 @@ class BulkAdd extends Extension {
set_time_limit(0); set_time_limit(0);
$bae = new BulkAddEvent($_POST['dir']); $bae = new BulkAddEvent($_POST['dir']);
send_event($bae); send_event($bae);
if(strlen($bae->results) > 0) { if(is_array($bae->results)) {
foreach($bae->results as $result) {
$this->theme->add_status("Adding files", $result);
}
} else if(strlen($bae->results) > 0) {
$this->theme->add_status("Adding files", $bae->results); $this->theme->add_status("Adding files", $bae->results);
} }
$this->theme->display_upload_results($page); $this->theme->display_upload_results($page);

View File

@ -1,7 +1,7 @@
<?php <?php
class BulkAddTheme extends Themelet { class BulkAddTheme extends Themelet {
var $messages = array(); private $messages = array();
/* /*
* Show a standard page for results to be put into * Show a standard page for results to be put into
@ -12,9 +12,9 @@ class BulkAddTheme extends Themelet {
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
$html = ""; $html = "";
foreach($this->messages as $block) { foreach($this->messages as $block) {
$html .= "<br/>" . html_escape($html); $html .= "<br/>" . $block->body;
} }
$page->add_block(new Block("Results", $block)); $page->add_block(new Block("Results", $html));
} }
/* /*

View File

@ -73,7 +73,7 @@ class BulkAddCSV extends Extension {
$metadata = array(); $metadata = array();
$metadata['filename'] = $pathinfo['basename']; $metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension']; $metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = $tags; $metadata['tags'] = Tag::explode($tags);
$metadata['source'] = $source; $metadata['source'] = $source;
$event = new DataUploadEvent($tmpname, $metadata); $event = new DataUploadEvent($tmpname, $metadata);
send_event($event); send_event($event);

View File

@ -1,7 +1,7 @@
<?php <?php
class BulkAddCSVTheme extends Themelet { class BulkAddCSVTheme extends Themelet {
var $messages = array(); private $messages = array();
/* /*
* Show a standard page for results to be put into * Show a standard page for results to be put into

View File

@ -10,14 +10,22 @@
$admin = loggedIn(); $admin = loggedIn();
$log = 1;
if (isset($_GET['log'])) if (isset($_GET['log']))
{
$log = $_GET['log']; $log = $_GET['log'];
}
if (isset($_POST['log'])) if (isset($_POST['log']))
{
$log = $_POST['log']; $log = $_POST['log'];
}
if (!isset($log)) if (filter_var($log, FILTER_VALIDATE_INT) === false)
{
$log = 1; $log = 1;
}
$ys = ys($log); $ys = ys($log);
$posts = $ys->posts(); $posts = $ys->posts();

View File

@ -30,6 +30,7 @@ class Chatbox extends Extension {
// loads the chatbox at the set location // loads the chatbox at the set location
$html = "<div id=\"yshout\"></div>"; $html = "<div id=\"yshout\"></div>";
$chatblock = new Block("Chatbox", $html, "main", 97); $chatblock = new Block("Chatbox", $html, "main", 97);
$chatblock->is_content = false;
$page->add_block($chatblock); $page->add_block($chatblock);
} }
} }

View File

@ -89,7 +89,10 @@
global $yShout, $prefs; global $yShout, $prefs;
if ($yShout) return $yShout; if ($yShout) return $yShout;
if ($log > $prefs['logs'] || $log < 0 || !is_numeric($log)) $log = 1; if (filter_var($log, FILTER_VALIDATE_INT, array("options" => array("min_range" => 0, "max_range" => $prefs['logs']))) === false)
{
$log = 1;
}
$log = 'log.' . $log; $log = 'log.' . $log;
return new YShout($log, loggedIn()); return new YShout($log, loggedIn());

View File

@ -9,7 +9,7 @@
* Formatting is done with the standard formatting API (normally BBCode) * Formatting is done with the standard formatting API (normally BBCode)
*/ */
require_once "lib/akismet.class.php"; require_once "vendor/ifixit/php-akismet/akismet.class.php";
class CommentPostingEvent extends Event { class CommentPostingEvent extends Event {
/** @var int */ /** @var int */
@ -53,9 +53,9 @@ class CommentDeletionEvent extends Event {
class CommentPostingException extends SCoreException {} class CommentPostingException extends SCoreException {}
class Comment { class Comment {
var $owner, $owner_id, $owner_name, $owner_email, $owner_class; public $owner, $owner_id, $owner_name, $owner_email, $owner_class;
var $comment, $comment_id; public $comment, $comment_id;
var $image_id, $poster_ip, $posted; public $image_id, $poster_ip, $posted;
public function __construct($row) { public function __construct($row) {
$this->owner = null; $this->owner = null;
@ -94,7 +94,7 @@ class Comment {
class CommentList extends Extension { class CommentList extends Extension {
/** @var CommentListTheme $theme */ /** @var CommentListTheme $theme */
var $theme; public $theme;
public function onInitExt(InitExtEvent $event) { public function onInitExt(InitExtEvent $event) {
global $config, $database; global $config, $database;

View File

@ -1,11 +1,11 @@
<?php <?php
class CommentListTheme extends Themelet { class CommentListTheme extends Themelet {
var $comments_shown = 0; private $comments_shown = 0;
var $show_anon_id = false; private $show_anon_id = false;
var $anon_id = 1; private $anon_id = 1;
var $anon_cid = 0; private $anon_cid = 0;
var $anon_map = array(); private $anon_map = array();
var $ct = null; private $ct = null;
private function get_anon_colour($ip) { private function get_anon_colour($ip) {
if(is_null($this->ct)) { if(is_null($this->ct)) {
@ -259,8 +259,6 @@ class CommentListTheme extends Themelet {
else { else {
$h_userlink = '<a class="username" href="'.make_link('user/'.$h_name).'">'.$h_name.'</a>'; $h_userlink = '<a class="username" href="'.make_link('user/'.$h_name).'">'.$h_name.'</a>';
} }
$stripped_nonl = str_replace("\n", "\\n", substr($tfe->stripped, 0, 50));
$stripped_nonl = str_replace("\r", "\\r", $stripped_nonl);
$hb = ($comment->owner_class == "hellbanned" ? "hb" : ""); $hb = ($comment->owner_class == "hellbanned" ? "hb" : "");
if($trim) { if($trim) {
@ -276,13 +274,18 @@ class CommentListTheme extends Themelet {
if(!empty($comment->owner_email)) { if(!empty($comment->owner_email)) {
$hash = md5(strtolower($comment->owner_email)); $hash = md5(strtolower($comment->owner_email));
$cb = date("Y-m-d"); $cb = date("Y-m-d");
$h_avatar = "<img src=\"http://www.gravatar.com/avatar/$hash.jpg?cacheBreak=$cb\"><br>"; $h_avatar = "<img src=\"//www.gravatar.com/avatar/$hash.jpg?cacheBreak=$cb\"><br>";
} }
$h_reply = " - <a href='javascript: replyTo($i_image_id, $i_comment_id, \"$h_name\")'>Reply</a>"; $h_reply = " - <a href='javascript: replyTo($i_image_id, $i_comment_id, \"$h_name\")'>Reply</a>";
$h_ip = $user->can("view_ip") ? "<br>".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : ""; $h_ip = $user->can("view_ip") ? "<br>".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : "";
$h_del = $user->can("delete_comment") ? $h_del = "";
' - <a onclick="return confirm(\'Delete comment by '.$h_name.':\\n'.$stripped_nonl.'\');" '. if ($user->can("delete_comment")) {
'href="'.make_link('comment/delete/'.$i_comment_id.'/'.$i_image_id).'">Del</a>' : ''; $comment_preview = substr(html_unescape($tfe->stripped), 0, 50);
$j_delete_confirm_message = json_encode("Delete comment by {$comment->owner_name}:\n$comment_preview");
$h_delete_script = html_escape("return confirm($j_delete_confirm_message);");
$h_delete_link = make_link("comment/delete/$i_comment_id/$i_image_id");
$h_del = " - <a onclick='$h_delete_script' href='$h_delete_link'>Del</a>";
}
$html = " $html = "
<div class=\"comment $hb\" id=\"c$i_comment_id\"> <div class=\"comment $hb\" id=\"c$i_comment_id\">
<div class=\"info\"> <div class=\"info\">

View File

@ -213,7 +213,7 @@ class CronUploader extends Extension {
/** /**
* Returns amount of files & total size of dir. * Returns amount of files & total size of dir.
* @param $path string * @param string $path directory name to scan
* @return multitype:number * @return multitype:number
*/ */
function scan_dir($path){ function scan_dir($path){
@ -234,7 +234,7 @@ class CronUploader extends Extension {
/** /**
* Uploads the image & handles everything * Uploads the image & handles everything
* @param $upload_count int to upload a non-config amount of imgs * @param int $upload_count to upload a non-config amount of imgs
* @return boolean returns true if the upload was successful * @return boolean returns true if the upload was successful
*/ */
public function process_upload($upload_count = 0) { public function process_upload($upload_count = 0) {
@ -304,9 +304,14 @@ class CronUploader extends Extension {
/** /**
* Generate the necessary DataUploadEvent for a given image and tags. * Generate the necessary DataUploadEvent for a given image and tags.
*
* @param string $tmpname
* @param string $filename
* @param string $tags
*/ */
private function add_image($tmpname, $filename, $tags) { private function add_image($tmpname, $filename, $tags) {
assert ( file_exists ( $tmpname ) ); assert ( file_exists ( $tmpname ) );
assert('is_string($tags)');
$pathinfo = pathinfo ( $filename ); $pathinfo = pathinfo ( $filename );
if (! array_key_exists ( 'extension', $pathinfo )) { if (! array_key_exists ( 'extension', $pathinfo )) {
@ -315,7 +320,7 @@ class CronUploader extends Extension {
$metadata = array(); $metadata = array();
$metadata ['filename'] = $pathinfo ['basename']; $metadata ['filename'] = $pathinfo ['basename'];
$metadata ['extension'] = $pathinfo ['extension']; $metadata ['extension'] = $pathinfo ['extension'];
$metadata ['tags'] = ""; // = $tags; doesn't work when not logged in here $metadata ['tags'] = array(); // = $tags; doesn't work when not logged in here
$metadata ['source'] = null; $metadata ['source'] = null;
$event = new DataUploadEvent ( $tmpname, $metadata ); $event = new DataUploadEvent ( $tmpname, $metadata );
send_event ( $event ); send_event ( $event );
@ -329,7 +334,7 @@ class CronUploader extends Extension {
// Set tags // Set tags
$img = Image::by_id($event->image_id); $img = Image::by_id($event->image_id);
$img->set_tags($tags); $img->set_tags(Tag::explode($tags));
} }
private function generate_image_queue($base = "", $subdir = "") { private function generate_image_queue($base = "", $subdir = "") {

View File

@ -12,16 +12,18 @@
* extensions and read their documentation * extensions and read their documentation
*/ */
/** @private */ /**
* @private
* @return int
*/
function __extman_extcmp(ExtensionInfo $a, ExtensionInfo $b) { function __extman_extcmp(ExtensionInfo $a, ExtensionInfo $b) {
return strcmp($a->name, $b->name); return strcmp($a->name, $b->name);
} }
/** @private */
class ExtensionInfo { class ExtensionInfo {
var $ext_name, $name, $link, $author, $email; public $ext_name, $name, $link, $author, $email;
var $description, $documentation, $version, $visibility; public $description, $documentation, $version, $visibility;
var $enabled; public $enabled;
public function __construct($main) { public function __construct($main) {
$matches = array(); $matches = array();
@ -190,6 +192,9 @@ class ExtManager extends Extension {
$this->write_config($extras); $this->write_config($extras);
} }
/**
* @param string[] $extras
*/
private function write_config($extras) { private function write_config($extras) {
file_put_contents( file_put_contents(
"data/config/extensions.conf.php", "data/config/extensions.conf.php",

View File

@ -1,37 +1,42 @@
<?php <?php
class ExtManagerTheme extends Themelet { class ExtManagerTheme extends Themelet {
/**
* @param Page $page
* @param ExtensionInfo[] $extensions
* @param bool $editable
*/
public function display_table(Page $page, /*array*/ $extensions, /*bool*/ $editable) { public function display_table(Page $page, /*array*/ $extensions, /*bool*/ $editable) {
$h_en = $editable ? "<th>Enabled</th>" : ""; $h_en = $editable ? "<th>Enabled</th>" : "";
$html = " $html = "
".make_form(make_link("ext_manager/set"))." ".make_form(make_link("ext_manager/set"))."
<script type='text/javascript'> <table id='extensions' class='zebra sortable'>
$(document).ready(function() {
$(\"#extensions\").tablesorter();
});
</script>
<table id='extensions' class='zebra'>
<thead> <thead>
<tr>$h_en<th>Name</th><th>Description</th></tr> <tr>
$h_en
<th>Name</th>
<th>Docs</th>
<th>Description</th>
</tr>
</thead> </thead>
<tbody> <tbody>
"; ";
foreach($extensions as $extension) { foreach($extensions as $extension) {
if(!$editable && $extension->visibility == "admin") continue; if(!$editable && $extension->visibility == "admin") continue;
$h_name = html_escape(empty($extension->name) ? $extension->ext_name : $extension->name); $h_name = html_escape(empty($extension->name) ? $extension->ext_name : $extension->name);
$h_description = html_escape($extension->description); $h_description = html_escape($extension->description);
if($extension->enabled === TRUE) $h_enabled = " checked='checked'"; $h_link = make_link("ext_doc/".url_escape($extension->ext_name));
else if($extension->enabled === FALSE) $h_enabled = ""; $h_enabled = ($extension->enabled === TRUE ? " checked='checked'" : ($extension->enabled === FALSE ? "" : " disabled checked='checked'"));
else $h_enabled = " disabled checked='checked'"; $h_enabled_box = $editable ? "<td><input type='checkbox' name='ext_".html_escape($extension->ext_name)."'$h_enabled></td>" : "";
$h_link = make_link("ext_doc/".url_escape($extension->ext_name)); $h_docs = ($extension->documentation ? "<a href='$h_link'>■</a>" : ""); //TODO: A proper "docs" symbol would be preferred here.
$h_en = $editable ? "<td><input type='checkbox' name='ext_".html_escape($extension->ext_name)."'$h_enabled></td>" : "";
$html .= " $html .= "
<tr> <tr data-ext='{$extension->ext_name}'>
$h_en {$h_enabled_box}
<td><a href='$h_link'>$h_name</a></td> <td>{$h_name}</td>
<td style='text-align: left;'>$h_description</td> <td>{$h_docs}</td>
<td style='text-align: left;'>{$h_description}</td>
</tr>"; </tr>";
} }
$h_set = $editable ? "<tfoot><tr><td colspan='5'><input type='submit' value='Set Extensions'></td></tr></tfoot>" : ""; $h_set = $editable ? "<tfoot><tr><td colspan='5'><input type='submit' value='Set Extensions'></td></tr></tfoot>" : "";

View File

@ -87,328 +87,347 @@ class Forum extends Extension {
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event) {
global $page, $user; global $page, $user;
if($event->page_matches("forum")) { if($event->page_matches("forum")) {
switch($event->get_arg(0)) { switch($event->get_arg(0)) {
case "index": case "index":
{ $this->show_last_threads($page, $event, $user->is_admin());
$this->show_last_threads($page, $event, $user->is_admin()); if(!$user->is_anonymous()) $this->theme->display_new_thread_composer($page);
if(!$user->is_anonymous()) $this->theme->display_new_thread_composer($page); break;
break; case "view":
} $threadID = int_escape($event->get_arg(1));
case "view": $pageNumber = int_escape($event->get_arg(2));
{ list($errors) = $this->sanity_check_viewed_thread($threadID);
$threadID = int_escape($event->get_arg(1));
$pageNumber = int_escape($event->get_arg(2)); if($errors!=null)
list($errors) = $this->sanity_check_viewed_thread($threadID); {
$this->theme->display_error(500, "Error", $errors);
break;
}
$this->show_posts($event, $user->is_admin());
if($user->is_admin()) $this->theme->add_actions_block($page, $threadID);
if(!$user->is_anonymous()) $this->theme->display_new_post_composer($page, $threadID);
break;
case "new":
global $page;
$this->theme->display_new_thread_composer($page);
break;
case "create":
$redirectTo = "forum/index";
if (!$user->is_anonymous())
{
list($errors) = $this->sanity_check_new_thread();
if($errors!=null) if($errors!=null)
{ {
$this->theme->display_error(500, "Error", $errors); $this->theme->display_error(500, "Error", $errors);
break; break;
} }
$this->show_posts($event, $user->is_admin()); $newThreadID = $this->save_new_thread($user);
if($user->is_admin()) $this->theme->add_actions_block($page, $threadID); $this->save_new_post($newThreadID, $user);
if(!$user->is_anonymous()) $this->theme->display_new_post_composer($page, $threadID); $redirectTo = "forum/view/".$newThreadID."/1";
break; }
}
case "new":
{
global $page;
$this->theme->display_new_thread_composer($page);
break;
}
case "create":
{
$redirectTo = "forum/index";
if (!$user->is_anonymous())
{
list($errors) = $this->sanity_check_new_thread();
if($errors!=null) $page->set_mode("redirect");
{ $page->set_redirect(make_link($redirectTo));
$this->theme->display_error(500, "Error", $errors);
break;
}
$newThreadID = $this->save_new_thread($user); break;
$this->save_new_post($newThreadID, $user); case "delete":
$redirectTo = "forum/view/".$newThreadID."/1"; $threadID = int_escape($event->get_arg(1));
} $postID = int_escape($event->get_arg(2));
$page->set_mode("redirect"); if ($user->is_admin()) {$this->delete_post($postID);}
$page->set_redirect(make_link($redirectTo));
break; $page->set_mode("redirect");
} $page->set_redirect(make_link("forum/view/".$threadID));
case "delete": break;
$threadID = int_escape($event->get_arg(1)); case "nuke":
$postID = int_escape($event->get_arg(2)); $threadID = int_escape($event->get_arg(1));
if ($user->is_admin()) {$this->delete_post($postID);} if ($user->is_admin())
$this->delete_thread($threadID);
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("forum/view/".$threadID)); $page->set_redirect(make_link("forum/index"));
break; break;
case "nuke": case "answer":
$threadID = int_escape($event->get_arg(1)); $threadID = int_escape($_POST["threadID"]);
$total_pages = $this->get_total_pages_for_thread($threadID);
if (!$user->is_anonymous())
{
list($errors) = $this->sanity_check_new_post();
if ($user->is_admin()) if ($errors!=null)
$this->delete_thread($threadID); {
$this->theme->display_error(500, "Error", $errors);
$page->set_mode("redirect"); break;
$page->set_redirect(make_link("forum/index")); }
break; $this->save_new_post($threadID, $user);
case "answer": }
$threadID = int_escape($_POST["threadID"]); $page->set_mode("redirect");
$total_pages = $this->get_total_pages_for_thread($threadID); $page->set_redirect(make_link("forum/view/".$threadID."/".$total_pages));
if (!$user->is_anonymous()) break;
{ default:
list($errors) = $this->sanity_check_new_post(); $page->set_mode("redirect");
$page->set_redirect(make_link("forum/index"));
if ($errors!=null) //$this->theme->display_error(400, "Invalid action", "You should check forum/index.");
{ break;
$this->theme->display_error(500, "Error", $errors);
break;
}
$this->save_new_post($threadID, $user);
}
$page->set_mode("redirect");
$page->set_redirect(make_link("forum/view/".$threadID."/".$total_pages));
break;
default:
{
$page->set_mode("redirect");
$page->set_redirect(make_link("forum/index"));
//$this->theme->display_error(400, "Invalid action", "You should check forum/index.");
break;
}
}
}
}
private function get_total_pages_for_thread(/*int*/ $threadID)
{
global $database, $config;
$result = $database->get_row("SELECT COUNT(1) AS count FROM forum_posts WHERE thread_id = ?", array($threadID));
return ceil($result["count"] / $config->get_int("forumPostsPerPage"));
}
private function sanity_check_new_thread()
{
$errors = null;
if (!array_key_exists("title", $_POST))
{
$errors .= "<div id='error'>No title supplied.</div>";
}
else if (strlen($_POST["title"]) == 0)
{
$errors .= "<div id='error'>You cannot have an empty title.</div>";
}
else if (strlen(html_escape($_POST["title"])) > 255)
{
$errors .= "<div id='error'>Your title is too long.</div>";
}
if (!array_key_exists("message", $_POST))
{
$errors .= "<div id='error'>No message supplied.</div>";
}
else if (strlen($_POST["message"]) == 0)
{
$errors .= "<div id='error'>You cannot have an empty message.</div>";
}
return array($errors);
}
private function sanity_check_new_post()
{
$errors = null;
if (!array_key_exists("threadID", $_POST))
{
$errors = "<div id='error'>No thread ID supplied.</div>";
}
else if (strlen($_POST["threadID"]) == 0)
{
$errors = "<div id='error'>No thread ID supplied.</div>";
}
else if (is_numeric($_POST["threadID"]))
if (!array_key_exists("message", $_POST))
{
$errors .= "<div id='error'>No message supplied.</div>";
}
else if (strlen($_POST["message"]) == 0)
{
$errors .= "<div id='error'>You cannot have an empty message.</div>";
}
return array($errors);
}
private function sanity_check_viewed_thread($threadID)
{
$errors = null;
if (!$this->threadExists($threadID))
{
$errors = "<div id='error'>Inexistent thread.</div>";
}
return array($errors);
}
private function get_thread_title($threadID)
{
global $database;
$result = $database->get_row("SELECT t.title FROM forum_threads AS t WHERE t.id = ? ", array($threadID));
return $result["title"];
}
private function show_last_threads(Page $page, $event, $showAdminOptions = false)
{
global $config, $database;
$pageNumber = $event->get_arg(1);
$threadsPerPage = $config->get_int('forumThreadsPerPage', 15);
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_threads") / $threadsPerPage);
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else if ($pageNumber >= $totalPages)
$pageNumber = $totalPages - 1;
else
$pageNumber--;
$threads = $database->get_all(
"SELECT f.id, f.sticky, f.title, f.date, f.uptodate, u.name AS user_name, u.email AS user_email, u.class AS user_class, sum(1) - 1 AS response_count ".
"FROM forum_threads AS f ".
"INNER JOIN users AS u ".
"ON f.user_id = u.id ".
"INNER JOIN forum_posts AS p ".
"ON p.thread_id = f.id ".
"GROUP BY f.id, f.sticky, f.title, f.date, u.name, u.email, u.class ".
"ORDER BY f.sticky ASC, f.uptodate DESC LIMIT :limit OFFSET :offset"
, array("limit"=>$threadsPerPage, "offset"=>$pageNumber * $threadsPerPage)
);
$this->theme->display_thread_list($page, $threads, $showAdminOptions, $pageNumber + 1, $totalPages);
}
private function show_posts($event, $showAdminOptions = false)
{
global $config, $database;
$threadID = $event->get_arg(1);
$pageNumber = $event->get_arg(2);
$postsPerPage = $config->get_int('forumPostsPerPage', 15);
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_posts WHERE thread_id = ?", array($threadID)) / $postsPerPage);
$threadTitle = $this->get_thread_title($threadID);
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else if ($pageNumber >= $totalPages)
$pageNumber = $totalPages - 1;
else
$pageNumber--;
$posts = $database->get_all(
"SELECT p.id, p.date, p.message, u.name as user_name, u.email AS user_email, u.class AS user_class ".
"FROM forum_posts AS p ".
"INNER JOIN users AS u ".
"ON p.user_id = u.id ".
"WHERE thread_id = :thread_id ".
"ORDER BY p.date ASC ".
"LIMIT :limit OFFSET :offset"
, array("thread_id"=>$threadID, "offset"=>$pageNumber * $postsPerPage, "limit"=>$postsPerPage)
);
$this->theme->display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber + 1, $totalPages);
}
private function save_new_thread($user)
{
$title = html_escape($_POST["title"]);
$sticky = !empty($_POST["sticky"]) ? html_escape($_POST["sticky"]) : "N";
if($sticky == ""){
$sticky = "N";
}
global $database;
$database->execute("
INSERT INTO forum_threads
(title, sticky, user_id, date, uptodate)
VALUES
(?, ?, ?, now(), now())",
array($title, $sticky, $user->id));
$threadID = $database->get_last_insert_id("forum_threads_id_seq");
log_info("forum", "Thread {$threadID} created by {$user->name}");
return $threadID;
}
private function save_new_post($threadID, $user)
{
global $config;
$userID = $user->id;
$message = html_escape($_POST["message"]);
$max_characters = $config->get_int('forumMaxCharsPerPost');
$message = substr($message, 0, $max_characters);
global $database;
$database->execute("INSERT INTO forum_posts
(thread_id, user_id, date, message)
VALUES
(?, ?, now(), ?)"
, array($threadID, $userID, $message));
$postID = $database->get_last_insert_id("forum_posts_id_seq");
log_info("forum", "Post {$postID} created by {$user->name}");
$database->execute("UPDATE forum_threads SET uptodate=now() WHERE id=?", array ($threadID));
}
private function retrieve_posts($threadID, $pageNumber)
{
global $database, $config;
$postsPerPage = $config->get_int('forumPostsPerPage', 15);
return $database->get_all(
"SELECT p.id, p.date, p.message, u.name as user_name, u.email AS user_email, u.class AS user_class ".
"FROM forum_posts AS p ".
"INNER JOIN users AS u ".
"ON p.user_id = u.id ".
"WHERE thread_id = :thread_id ".
"ORDER BY p.date ASC ".
"LIMIT :limit OFFSET :offset "
, array("thread_id"=>$threadID, "offset"=>($pageNumber - 1) * $postsPerPage, "limit"=>$postsPerPage));
}
private function delete_thread($threadID)
{
global $database;
$database->execute("DELETE FROM forum_threads WHERE id = ?", array($threadID));
$database->execute("DELETE FROM forum_posts WHERE thread_id = ?", array($threadID));
}
private function delete_post($postID)
{
global $database;
$database->execute("DELETE FROM forum_posts WHERE id = ?", array($postID));
}
private function threadExists($threadID){
global $database;
$result=$database->get_one("SELECT EXISTS (SELECT * FROM forum_threads WHERE id= ?)", array($threadID));
if ($result==1){
return true;
}else{
return false;
} }
} }
} }
/**
* @param int $threadID
*/
private function get_total_pages_for_thread($threadID)
{
global $database, $config;
$result = $database->get_row("SELECT COUNT(1) AS count FROM forum_posts WHERE thread_id = ?", array($threadID));
return ceil($result["count"] / $config->get_int("forumPostsPerPage"));
}
private function sanity_check_new_thread()
{
$errors = null;
if (!array_key_exists("title", $_POST))
{
$errors .= "<div id='error'>No title supplied.</div>";
}
else if (strlen($_POST["title"]) == 0)
{
$errors .= "<div id='error'>You cannot have an empty title.</div>";
}
else if (strlen(html_escape($_POST["title"])) > 255)
{
$errors .= "<div id='error'>Your title is too long.</div>";
}
if (!array_key_exists("message", $_POST))
{
$errors .= "<div id='error'>No message supplied.</div>";
}
else if (strlen($_POST["message"]) == 0)
{
$errors .= "<div id='error'>You cannot have an empty message.</div>";
}
return array($errors);
}
private function sanity_check_new_post()
{
$errors = null;
if (!array_key_exists("threadID", $_POST))
{
$errors = "<div id='error'>No thread ID supplied.</div>";
}
else if (strlen($_POST["threadID"]) == 0)
{
$errors = "<div id='error'>No thread ID supplied.</div>";
}
else if (is_numeric($_POST["threadID"]))
if (!array_key_exists("message", $_POST))
{
$errors .= "<div id='error'>No message supplied.</div>";
}
else if (strlen($_POST["message"]) == 0)
{
$errors .= "<div id='error'>You cannot have an empty message.</div>";
}
return array($errors);
}
/**
* @param int $threadID
*/
private function sanity_check_viewed_thread($threadID)
{
$errors = null;
if (!$this->threadExists($threadID))
{
$errors = "<div id='error'>Inexistent thread.</div>";
}
return array($errors);
}
/**
* @param int $threadID
*/
private function get_thread_title($threadID)
{
global $database;
$result = $database->get_row("SELECT t.title FROM forum_threads AS t WHERE t.id = ? ", array($threadID));
return $result["title"];
}
private function show_last_threads(Page $page, PageRequestEvent $event, $showAdminOptions = false)
{
global $config, $database;
$pageNumber = $event->get_arg(1);
$threadsPerPage = $config->get_int('forumThreadsPerPage', 15);
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_threads") / $threadsPerPage);
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else if ($pageNumber >= $totalPages)
$pageNumber = $totalPages - 1;
else
$pageNumber--;
$threads = $database->get_all(
"SELECT f.id, f.sticky, f.title, f.date, f.uptodate, u.name AS user_name, u.email AS user_email, u.class AS user_class, sum(1) - 1 AS response_count ".
"FROM forum_threads AS f ".
"INNER JOIN users AS u ".
"ON f.user_id = u.id ".
"INNER JOIN forum_posts AS p ".
"ON p.thread_id = f.id ".
"GROUP BY f.id, f.sticky, f.title, f.date, u.name, u.email, u.class ".
"ORDER BY f.sticky ASC, f.uptodate DESC LIMIT :limit OFFSET :offset"
, array("limit"=>$threadsPerPage, "offset"=>$pageNumber * $threadsPerPage)
);
$this->theme->display_thread_list($page, $threads, $showAdminOptions, $pageNumber + 1, $totalPages);
}
private function show_posts(PageRequestEvent $event, $showAdminOptions = false)
{
global $config, $database;
$threadID = $event->get_arg(1);
$pageNumber = $event->get_arg(2);
$postsPerPage = $config->get_int('forumPostsPerPage', 15);
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_posts WHERE thread_id = ?", array($threadID)) / $postsPerPage);
$threadTitle = $this->get_thread_title($threadID);
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else if ($pageNumber >= $totalPages)
$pageNumber = $totalPages - 1;
else
$pageNumber--;
$posts = $database->get_all(
"SELECT p.id, p.date, p.message, u.name as user_name, u.email AS user_email, u.class AS user_class ".
"FROM forum_posts AS p ".
"INNER JOIN users AS u ".
"ON p.user_id = u.id ".
"WHERE thread_id = :thread_id ".
"ORDER BY p.date ASC ".
"LIMIT :limit OFFSET :offset"
, array("thread_id"=>$threadID, "offset"=>$pageNumber * $postsPerPage, "limit"=>$postsPerPage)
);
$this->theme->display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber + 1, $totalPages);
}
private function save_new_thread(User $user)
{
$title = html_escape($_POST["title"]);
$sticky = !empty($_POST["sticky"]) ? html_escape($_POST["sticky"]) : "N";
if($sticky == ""){
$sticky = "N";
}
global $database;
$database->execute("
INSERT INTO forum_threads
(title, sticky, user_id, date, uptodate)
VALUES
(?, ?, ?, now(), now())",
array($title, $sticky, $user->id));
$threadID = $database->get_last_insert_id("forum_threads_id_seq");
log_info("forum", "Thread {$threadID} created by {$user->name}");
return $threadID;
}
/**
* @param int $threadID
*/
private function save_new_post($threadID, User $user)
{
global $config;
$userID = $user->id;
$message = html_escape($_POST["message"]);
$max_characters = $config->get_int('forumMaxCharsPerPost');
$message = substr($message, 0, $max_characters);
global $database;
$database->execute("INSERT INTO forum_posts
(thread_id, user_id, date, message)
VALUES
(?, ?, now(), ?)"
, array($threadID, $userID, $message));
$postID = $database->get_last_insert_id("forum_posts_id_seq");
log_info("forum", "Post {$postID} created by {$user->name}");
$database->execute("UPDATE forum_threads SET uptodate=now() WHERE id=?", array ($threadID));
}
/**
* @param int $threadID
* @param int $pageNumber
*/
private function retrieve_posts($threadID, $pageNumber)
{
global $database, $config;
$postsPerPage = $config->get_int('forumPostsPerPage', 15);
return $database->get_all(
"SELECT p.id, p.date, p.message, u.name as user_name, u.email AS user_email, u.class AS user_class ".
"FROM forum_posts AS p ".
"INNER JOIN users AS u ".
"ON p.user_id = u.id ".
"WHERE thread_id = :thread_id ".
"ORDER BY p.date ASC ".
"LIMIT :limit OFFSET :offset "
, array("thread_id"=>$threadID, "offset"=>($pageNumber - 1) * $postsPerPage, "limit"=>$postsPerPage));
}
/**
* @param int $threadID
*/
private function delete_thread($threadID)
{
global $database;
$database->execute("DELETE FROM forum_threads WHERE id = ?", array($threadID));
$database->execute("DELETE FROM forum_posts WHERE thread_id = ?", array($threadID));
}
/**
* @param int $postID
*/
private function delete_post($postID)
{
global $database;
$database->execute("DELETE FROM forum_posts WHERE id = ?", array($postID));
}
/**
* @param int $threadID
*/
private function threadExists($threadID)
{
global $database;
$result=$database->get_one("SELECT EXISTS (SELECT * FROM forum_threads WHERE id= ?)", array($threadID));
if ($result==1){
return true;
}else{
return false;
}
}
}

View File

@ -43,10 +43,7 @@ class Handle404 extends Extension {
private function count_main($blocks) { private function count_main($blocks) {
$n = 0; $n = 0;
foreach($blocks as $block) { foreach($blocks as $block) {
if($block->section == "main") $n++; // more hax. if($block->section == "main" && $block->is_content) $n++; // more hax.
}
if(ext_is_live("Chatbox")) {
$n--; // even more hax.
} }
return $n; return $n;
} }

View File

@ -35,8 +35,10 @@ class ArchiveFileHandler extends Extension {
exec($cmd); exec($cmd);
$results = add_dir($tmpdir); $results = add_dir($tmpdir);
if(count($results) > 0) { if(count($results) > 0) {
// FIXME no theme? // Not all themes have the add_status() method, so need to check before calling.
$this->theme->add_status("Adding files", $results); if (method_exists($this->theme, "add_status")) {
$this->theme->add_status("Adding files", $results);
}
} }
deltree($tmpdir); deltree($tmpdir);
$event->image_id = -2; // default -1 = upload wasn't handled $event->image_id = -2; // default -1 = upload wasn't handled

View File

@ -37,7 +37,7 @@ class FlashFileHandler extends DataHandlerExtension {
$image->hash = $metadata['hash']; $image->hash = $metadata['hash'];
$image->filename = $metadata['filename']; $image->filename = $metadata['filename'];
$image->ext = $metadata['extension']; $image->ext = $metadata['extension'];
$image->tag_array = Tag::explode($metadata['tags']); $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
$image->source = $metadata['source']; $image->source = $metadata['source'];
$info = getimagesize($filename); $info = getimagesize($filename);

View File

@ -10,7 +10,7 @@ class IcoFileHandler extends Extension {
if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) { if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
$hash = $event->hash; $hash = $event->hash;
$ha = substr($hash, 0, 2); $ha = substr($hash, 0, 2);
if(!move_upload_to_archive($event)) return; move_upload_to_archive($event);
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata); $image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
if(is_null($image)) { if(is_null($image)) {
@ -35,20 +35,6 @@ class IcoFileHandler extends Extension {
} }
} }
public function onPageRequest(PageRequestEvent $event) {
global $page;
if($event->page_matches("get_ico")) {
$id = int_escape($event->get_arg(0));
$image = Image::by_id($id);
$hash = $image->hash;
$ha = substr($hash, 0, 2);
$page->set_type("image/x-icon");
$page->set_mode("data");
$page->set_data(file_get_contents("images/$ha/$hash"));
}
}
/** /**
* @param string $ext * @param string $ext
* @return bool * @return bool
@ -67,38 +53,40 @@ class IcoFileHandler extends Extension {
$image = new Image(); $image = new Image();
$fp = fopen($filename, "r"); $fp = fopen($filename, "r");
$header = unpack("snull/stype/scount", fread($fp, 6)); $header = unpack("Snull/Stype/Scount", fread($fp, 6));
$subheader = unpack("cwidth/cheight/ccolours/cnull/splanes/sbpp/lsize/loffset", fread($fp, 16)); $subheader = unpack("Cwidth/Cheight/Ccolours/Cnull/Splanes/Sbpp/Lsize/loffset", fread($fp, 16));
fclose($fp); fclose($fp);
$image->width = $subheader['width']; $width = $subheader['width'];
$image->height = $subheader['height']; $height = $subheader['height'];
$image->width = $width == 0 ? 256 : $width;
$image->height = $height == 0 ? 256 : $height;
$image->filesize = $metadata['size']; $image->filesize = $metadata['size'];
$image->hash = $metadata['hash']; $image->hash = $metadata['hash'];
$image->filename = $metadata['filename']; $image->filename = $metadata['filename'];
$image->ext = $metadata['extension']; $image->ext = $metadata['extension'];
$image->tag_array = Tag::explode($metadata['tags']); $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
$image->source = $metadata['source']; $image->source = $metadata['source'];
return $image; return $image;
} }
/** /**
* @param $file * @param string $file
* @return bool * @return bool
*/ */
private function check_contents($file) { private function check_contents($file) {
if(!file_exists($file)) return false; if(!file_exists($file)) return false;
$fp = fopen($file, "r"); $fp = fopen($file, "r");
$header = unpack("snull/stype/scount", fread($fp, 6)); $header = unpack("Snull/Stype/Scount", fread($fp, 6));
fclose($fp); fclose($fp);
return ($header['null'] == 0 && ($header['type'] == 0 || $header['type'] == 1)); return ($header['null'] == 0 && ($header['type'] == 0 || $header['type'] == 1));
} }
/** /**
* @param $hash * @param string $hash
* @return bool * @return bool
*/ */
private function create_thumb($hash) { private function create_thumb($hash) {

View File

@ -4,7 +4,6 @@ class IcoHandlerTest extends ShimmiePHPUnitTestCase {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("lib/static/favicon.ico", "shimmie favicon"); $image_id = $this->post_image("lib/static/favicon.ico", "shimmie favicon");
$this->get_page("post/view/$image_id"); // test for no crash $this->get_page("post/view/$image_id"); // test for no crash
$this->get_page("get_ico/$image_id"); // test for no crash
# FIXME: test that the thumb works # FIXME: test that the thumb works
# FIXME: test that it gets displayed properly # FIXME: test that it gets displayed properly

View File

@ -2,9 +2,10 @@
class IcoFileHandlerTheme extends Themelet { class IcoFileHandlerTheme extends Themelet {
public function display_image(Page $page, Image $image) { public function display_image(Page $page, Image $image) {
$ilink = make_link("get_ico/{$image->id}/{$image->id}.ico"); $ilink = $image->get_image_link();
$html = " $html = "
<img id='main_image' src='$ilink'> <img id='main_image' class='shm-main-image' alt='main image' src='$ilink'
data-width='{$image->width}' data-height='{$image->height}'>
"; ";
$page->add_block(new Block("Image", $html, "main", 10)); $page->add_block(new Block("Image", $html, "main", 10));
} }

75
ext/handle_mp3/lib/jsmediatags.min.js vendored Normal file
View File

@ -0,0 +1,75 @@
(function(v){"object"===typeof exports&&"undefined"!==typeof module?module.exports=v():"function"===typeof define&&define.amd?define([],v):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).jsmediatags=v()})(function(){return function f(h,m,k){function l(b,a){if(!m[b]){if(!h[b]){var e="function"==typeof require&&require;if(!a&&e)return e(b,!0);if(d)return d(b,!0);e=Error("Cannot find module '"+b+"'");throw e.code="MODULE_NOT_FOUND",e;}e=m[b]=
{exports:{}};h[b][0].call(e.exports,function(a){var e=h[b][1][a];return l(e?e:a)},e,e.exports,f,h,m,k)}return m[b].exports}for(var d="function"==typeof require&&require,c=0;c<k.length;c++)l(k[c]);return l}({1:[function(f,h,m){},{}],2:[function(f,h,m){h.exports=XMLHttpRequest},{}],3:[function(f,h,m){function k(d,c){if("function"!==typeof c&&null!==c)throw new TypeError("Super expression must either be null or a function, not "+typeof c);d.prototype=Object.create(c&&c.prototype,{constructor:{value:d,
enumerable:!1,writable:!0,configurable:!0}});c&&(Object.setPrototypeOf?Object.setPrototypeOf(d,c):d.__proto__=c)}var l=function(){function d(c,b){for(var a=0;a<b.length;a++){var e=b[a];e.enumerable=e.enumerable||!1;e.configurable=!0;"value"in e&&(e.writable=!0);Object.defineProperty(c,e.key,e)}}return function(c,b,a){b&&d(c.prototype,b);a&&d(c,a);return c}}();f=function(d){function c(b){if(!(this instanceof c))throw new TypeError("Cannot call a class as a function");var a;a=Object.getPrototypeOf(c).call(this);
if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");a=!a||"object"!==typeof a&&"function"!==typeof a?this:a;a._array=b;a._size=b.length;a._isInitialized=!0;return a}k(c,d);l(c,[{key:"init",value:function(b){setTimeout(b.onSuccess,0)}},{key:"loadRange",value:function(b,a){setTimeout(a.onSuccess,0)}},{key:"getByteAt",value:function(b){return this._array[b]}}],[{key:"canReadFile",value:function(b){return Array.isArray(b)||"function"===typeof Buffer&&Buffer.isBuffer(b)}}]);
return c}(f("./MediaFileReader"));h.exports=f},{"./MediaFileReader":10}],4:[function(f,h,m){function k(c,b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);c.prototype=Object.create(b&&b.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(c,b):c.__proto__=b)}var l=function(){function c(b,a){for(var e=0;e<a.length;e++){var g=a[e];g.enumerable=g.enumerable||
!1;g.configurable=!0;"value"in g&&(g.writable=!0);Object.defineProperty(b,g.key,g)}}return function(b,a,e){a&&c(b.prototype,a);e&&c(b,e);return b}}(),d=f("./ChunkedFileData");f=function(c){function b(a){if(!(this instanceof b))throw new TypeError("Cannot call a class as a function");var e;e=Object.getPrototypeOf(b).call(this);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");e=!e||"object"!==typeof e&&"function"!==typeof e?this:e;e._blob=a;e._fileData=
new d;return e}k(b,c);l(b,[{key:"_init",value:function(a){this._size=this._blob.size;setTimeout(a.onSuccess,1)}},{key:"loadRange",value:function(a,b){var g=this,c=(this._blob.slice||this._blob.mozSlice||this._blob.webkitSlice).call(this._blob,a[0],a[1]+1),d=new FileReader;d.onloadend=function(c){c=new Uint8Array(d.result);g._fileData.addData(a[0],c);b.onSuccess()};d.onerror=d.onabort=function(a){if(b.onError)b.onError({type:"blob",info:d.error})};d.readAsArrayBuffer(c)}},{key:"getByteAt",value:function(a){return this._fileData.getByteAt(a)}}],
[{key:"canReadFile",value:function(a){return"undefined"!==typeof Blob&&a instanceof Blob||"undefined"!==typeof File&&a instanceof File}}]);return b}(f("./MediaFileReader"));h.exports=f},{"./ChunkedFileData":5,"./MediaFileReader":10}],5:[function(f,h,m){var k=function(){function f(d,c){for(var b=0;b<c.length;b++){var a=c[b];a.enumerable=a.enumerable||!1;a.configurable=!0;"value"in a&&(a.writable=!0);Object.defineProperty(d,a.key,a)}}return function(d,c,b){c&&f(d.prototype,c);b&&f(d,b);return d}}();
f=function(){function f(){if(!(this instanceof f))throw new TypeError("Cannot call a class as a function");this._fileData=[]}k(f,null,[{key:"NOT_FOUND",get:function(){return-1}}]);k(f,[{key:"addData",value:function(d,c){var b=d+c.length-1,a=this._getChunkRange(d,b);if(-1===a.startIx)this._fileData.splice(a.insertIx||0,0,{offset:d,data:c});else{var e=this._fileData[a.startIx],g=this._fileData[a.endIx],b=b<g.offset+g.data.length-1,p={offset:Math.min(d,e.offset),data:c};d>e.offset&&(e=this._sliceData(e.data,
0,d-e.offset),p.data=this._concatData(e,c));b&&(e=this._sliceData(p.data,0,g.offset-p.offset),p.data=this._concatData(e,g.data));this._fileData.splice(a.startIx,a.endIx-a.startIx+1,p)}}},{key:"_concatData",value:function(d,c){if("undefined"!==typeof ArrayBuffer&&ArrayBuffer.isView(d)){var b=new d.constructor(d.length+c.length);b.set(d,0);b.set(c,d.length);return b}return d.concat(c)}},{key:"_sliceData",value:function(d,c,b){return d.slice?d.slice(c,b):d.subarray(c,b)}},{key:"_getChunkRange",value:function(d,
c){for(var b=-1,a=-1,e=0,g=0;g<this._fileData.length;g++,e=g){var p=this._fileData[g].offset,q=p+this._fileData[g].data.length;if(c<p-1)break;if(d<=q+1&&c>=p-1){b=g;break}}if(-1===b)return{startIx:-1,endIx:-1,insertIx:e};for(g=b;g<this._fileData.length&&!(p=this._fileData[g].offset,q=p+this._fileData[g].data.length,c>=p-1&&(a=g),c<=q+1);g++);-1===a&&(a=b);return{startIx:b,endIx:a}}},{key:"hasDataRange",value:function(d,c){for(var b=0;b<this._fileData.length;b++){var a=this._fileData[b];if(c<a.offset)break;
if(d>=a.offset&&c<a.offset+a.data.length)return!0}return!1}},{key:"getByteAt",value:function(d){for(var c,b=0;b<this._fileData.length;b++){var a=this._fileData[b].offset,e=a+this._fileData[b].data.length-1;if(d>=a&&d<=e){c=this._fileData[b];break}}if(c)return c.data[d-c.offset];throw Error("Offset "+d+" hasn't been loaded yet.");}}]);return f}();h.exports=f},{}],6:[function(f,h,m){function k(c,b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+
typeof b);c.prototype=Object.create(b&&b.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(c,b):c.__proto__=b)}var l=function(){function c(b,a){for(var e=0;e<a.length;e++){var g=a[e];g.enumerable=g.enumerable||!1;g.configurable=!0;"value"in g&&(g.writable=!0);Object.defineProperty(b,g.key,g)}}return function(b,a,e){a&&c(b.prototype,a);e&&c(b,e);return b}}();m=f("./MediaTagReader");f("./MediaFileReader");f=function(c){function b(){if(!(this instanceof
b))throw new TypeError("Cannot call a class as a function");var a=Object.getPrototypeOf(b).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!a||"object"!==typeof a&&"function"!==typeof a?this:a}k(b,c);l(b,[{key:"_loadData",value:function(a,b){var g=a.getSize();a.loadRange([g-128,g-1],b)}},{key:"_parseData",value:function(a,b){var g=a.getSize()-128,c=a.getStringWithCharsetAt(g+3,30).toString(),q=a.getStringWithCharsetAt(g+33,
30).toString(),r=a.getStringWithCharsetAt(g+63,30).toString(),f=a.getStringWithCharsetAt(g+93,4).toString(),u=a.getByteAt(g+97+28),n=a.getByteAt(g+97+29);if(0==u&&0!=n)var u="1.1",x=a.getStringWithCharsetAt(g+97,28).toString();else u="1.0",x=a.getStringWithCharsetAt(g+97,30).toString(),n=0;g=a.getByteAt(g+97+30);c={type:"ID3",version:u,tags:{title:c,artist:q,album:r,year:f,comment:x,genre:255>g?d[g]:""}};n&&(c.tags.track=n);return c}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:-128,
length:128}}},{key:"canReadTagFormat",value:function(a){return"TAG"===String.fromCharCode.apply(String,a.slice(0,3))}}]);return b}(m);var d="Blues;Classic Rock;Country;Dance;Disco;Funk;Grunge;Hip-Hop;Jazz;Metal;New Age;Oldies;Other;Pop;R&B;Rap;Reggae;Rock;Techno;Industrial;Alternative;Ska;Death Metal;Pranks;Soundtrack;Euro-Techno;Ambient;Trip-Hop;Vocal;Jazz+Funk;Fusion;Trance;Classical;Instrumental;Acid;House;Game;Sound Clip;Gospel;Noise;AlternRock;Bass;Soul;Punk;Space;Meditative;Instrumental Pop;Instrumental Rock;Ethnic;Gothic;Darkwave;Techno-Industrial;Electronic;Pop-Folk;Eurodance;Dream;Southern Rock;Comedy;Cult;Gangsta;Top 40;Christian Rap;Pop/Funk;Jungle;Native American;Cabaret;New Wave;Psychadelic;Rave;Showtunes;Trailer;Lo-Fi;Tribal;Acid Punk;Acid Jazz;Polka;Retro;Musical;Rock & Roll;Hard Rock;Folk;Folk-Rock;National Folk;Swing;Fast Fusion;Bebob;Latin;Revival;Celtic;Bluegrass;Avantgarde;Gothic Rock;Progressive Rock;Psychedelic Rock;Symphonic Rock;Slow Rock;Big Band;Chorus;Easy Listening;Acoustic;Humour;Speech;Chanson;Opera;Chamber Music;Sonata;Symphony;Booty Bass;Primus;Porn Groove;Satire;Slow Jam;Club;Tango;Samba;Folklore;Ballad;Power Ballad;Rhythmic Soul;Freestyle;Duet;Punk Rock;Drum Solo;Acapella;Euro-House;Dance Hall".split(";");
h.exports=f},{"./MediaFileReader":10,"./MediaTagReader":11}],7:[function(f,h,m){function k(b){var a;switch(b){case 0:a="iso-8859-1";break;case 1:a="utf-16";break;case 2:a="utf-16be";break;case 3:a="utf-8"}return a}function l(b,a,e,g){g=e.getStringWithCharsetAt(b+1,a-1,g);b=e.getStringWithCharsetAt(b+1+g.bytesReadCount,a-1-g.bytesReadCount);return{user_description:g.toString(),data:b.toString()}}f("./MediaFileReader");var d={APIC:function(b,a,e,g,p){p=p||"3";g=b;var d=k(e.getByteAt(b));switch(p){case "2":p=
e.getStringAt(b+1,3);b+=4;break;case "3":case "4":p=e.getStringWithCharsetAt(b+1,a-1);b+=1+p.bytesReadCount;break;default:throw Error("Couldn't read ID3v2 major version.");}var r=e.getByteAt(b,1),r=c[r],d=e.getStringWithCharsetAt(b+1,a-(b-g)-1,d);b+=1+d.bytesReadCount;return{format:p.toString(),type:r,description:d.toString(),data:e.getBytesAt(b,g+a-b)}},COMM:function(b,a,e,g,c){var d=b,r=k(e.getByteAt(b));g=e.getStringAt(b+1,3);c=e.getStringWithCharsetAt(b+4,a-4,r);b+=4+c.bytesReadCount;b=e.getStringWithCharsetAt(b,
d+a-b,r);return{language:g,short_description:c.toString(),text:b.toString()}}};d.COM=d.COMM;d.PIC=function(b,a,e,g,c){return d.APIC(b,a,e,g,"2")};d.PCNT=function(b,a,e,g,c){return e.getLongAt(b,!1)};d.CNT=d.PCNT;d["T*"]=function(b,a,e,g,c){g=k(e.getByteAt(b));return e.getStringWithCharsetAt(b+1,a-1,g).toString()};d.TXXX=function(b,a,e,g,c){g=k(e.getByteAt(b));return l(b,a,e,g)};d["W*"]=function(b,a,e,g,c){g=k(e.getByteAt(b));return void 0!==g?l(b,a,e,g):e.getStringWithCharsetAt(b,a,g).toString()};
d.TCON=function(b,a,e,g){return d["T*"].apply(this,arguments).replace(/^\(\d+\)/,"")};d.TCO=d.TCON;d.USLT=function(b,a,e,g,c){var d=b,r=k(e.getByteAt(b));g=e.getStringAt(b+1,3);c=e.getStringWithCharsetAt(b+4,a-4,r);b+=4+c.bytesReadCount;b=e.getStringWithCharsetAt(b,d+a-b,r);return{language:g,descriptor:c.toString(),lyrics:b.toString()}};d.ULT=d.USLT;var c="Other;32x32 pixels 'file icon' (PNG only);Other file icon;Cover (front);Cover (back);Leaflet page;Media (e.g. label side of CD);Lead artist/lead performer/soloist;Artist/performer;Conductor;Band/Orchestra;Composer;Lyricist/text writer;Recording Location;During recording;During performance;Movie/video screen capture;A bright coloured fish;Illustration;Band/artist logotype;Publisher/Studio logotype".split(";");
h.exports={getFrameReaderFunction:function(b){return b in d?d[b]:"T"===b[0]?d["T*"]:"W"===b[0]?d["W*"]:null}}},{"./MediaFileReader":10}],8:[function(f,h,m){function k(a,b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}var l=function(){function a(b,
e){for(var c=0;c<e.length;c++){var d=e[c];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(b,d.key,d)}}return function(b,c,d){c&&a(b.prototype,c);d&&a(b,d);return b}}();m=f("./MediaTagReader");f("./MediaFileReader");var d=f("./ArrayFileReader"),c=f("./ID3v2FrameReader");f=function(e){function g(){if(!(this instanceof g))throw new TypeError("Cannot call a class as a function");var a=Object.getPrototypeOf(g).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
return!a||"object"!==typeof a&&"function"!==typeof a?this:a}k(g,e);l(g,[{key:"_loadData",value:function(a,b){a.loadRange([6,9],{onSuccess:function(){a.loadRange([0,10+a.getSynchsafeInteger32At(6)-1],b)},onError:b.onError})}},{key:"_parseData",value:function(b,e){var c=0,g=b.getByteAt(c+3);if(4<g)return{type:"ID3",version:">2.4",tags:{}};var d=b.getByteAt(c+4),n=b.isBitSetAt(c+5,7),f=b.isBitSetAt(c+5,6),l=b.isBitSetAt(c+5,5),t=b.getSynchsafeInteger32At(c+6),c=c+10;if(f)var k=b.getLongAt(c,!0),c=c+
(k+4);var g={type:"ID3",version:"2."+g+"."+d,major:g,revision:d,flags:{unsynchronisation:n,extended_header:f,experimental_indicator:l,footer_present:!1},size:t,tags:{}},c=this._readFrames(c,t+10,b,g,e),h;for(h in a)a.hasOwnProperty(h)&&(t=this._getFrameData(c,a[h]))&&(g.tags[h]=t);for(var m in c)c.hasOwnProperty(m)&&(g.tags[m]=c[m]);return g}},{key:"_getUnsyncFileReader",value:function(a,b,c){a=a.getBytesAt(b,c);for(b=0;b<a.length-1;b++)255===a[b]&&0===a[b+1]&&a.splice(b+1,1);return new d(a)}},{key:"_readFrames",
value:function(a,b,e,g,d){var n={};for(d&&(d=this._expandShortcutTags(d));a<b;){var f=this._readFrameHeader(e,a,g),l=f.id;if(!l)break;var t=f.flags,h=f.size,k=a+f.headerSize,m=e;a+=f.headerSize+f.size;if(!d||-1!==d.indexOf(l)){if(g.flags.unsynchronisation||t&&t.format.unsynchronisation)m=this._getUnsyncFileReader(m,k,h),k=0,h=m.getSize();t&&t.format.data_length_indicator&&(k+=4,h-=4);t=(f=c.getFrameReaderFunction(l))?f(k,h,m,t):null;k=this._getFrameDescription(l);h={id:l,size:h,description:k,data:t};
l in n?(n[l].id&&(n[l]=[n[l]]),n[l].push(h)):n[l]=h}}return n}},{key:"_readFrameHeader",value:function(a,b,c){c=c.major;var e=null;switch(c){case 2:var g=a.getStringAt(b,3),d=a.getInteger24At(b+3,!0),f=6;break;case 3:g=a.getStringAt(b,4);d=a.getLongAt(b+4,!0);f=10;break;case 4:g=a.getStringAt(b,4),d=a.getSynchsafeInteger32At(b+4),f=10}if(g==String.fromCharCode(0,0,0)||g==String.fromCharCode(0,0,0,0))g="";g&&2<c&&(e=this._readFrameFlags(a,b+8));return{id:g||"",size:d||0,headerSize:f||0,flags:e}}},
{key:"_readFrameFlags",value:function(a,b){return{message:{tag_alter_preservation:a.isBitSetAt(b,6),file_alter_preservation:a.isBitSetAt(b,5),read_only:a.isBitSetAt(b,4)},format:{grouping_identity:a.isBitSetAt(b+1,7),compression:a.isBitSetAt(b+1,3),encryption:a.isBitSetAt(b+1,2),unsynchronisation:a.isBitSetAt(b+1,1),data_length_indicator:a.isBitSetAt(b+1,0)}}}},{key:"_getFrameData",value:function(a,b){for(var c=0,e;e=b[c];c++)if(e in a)return a[e].data}},{key:"_getFrameDescription",value:function(a){return a in
b?b[a]:"Unknown"}},{key:"getShortcuts",value:function(){return a}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:0,length:10}}},{key:"canReadTagFormat",value:function(a){return"ID3"===String.fromCharCode.apply(String,a.slice(0,3))}}]);return g}(m);var b={BUF:"Recommended buffer size",CNT:"Play counter",COM:"Comments",CRA:"Audio encryption",CRM:"Encrypted meta frame",ETC:"Event timing codes",EQU:"Equalization",GEO:"General encapsulated object",IPL:"Involved people list",LNK:"Linked information",
MCI:"Music CD Identifier",MLL:"MPEG location lookup table",PIC:"Attached picture",POP:"Popularimeter",REV:"Reverb",RVA:"Relative volume adjustment",SLT:"Synchronized lyric/text",STC:"Synced tempo codes",TAL:"Album/Movie/Show title",TBP:"BPM (Beats Per Minute)",TCM:"Composer",TCO:"Content type",TCR:"Copyright message",TDA:"Date",TDY:"Playlist delay",TEN:"Encoded by",TFT:"File type",TIM:"Time",TKE:"Initial key",TLA:"Language(s)",TLE:"Length",TMT:"Media type",TOA:"Original artist(s)/performer(s)",TOF:"Original filename",
TOL:"Original Lyricist(s)/text writer(s)",TOR:"Original release year",TOT:"Original album/Movie/Show title",TP1:"Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group",TP2:"Band/Orchestra/Accompaniment",TP3:"Conductor/Performer refinement",TP4:"Interpreted, remixed, or otherwise modified by",TPA:"Part of a set",TPB:"Publisher",TRC:"ISRC (International Standard Recording Code)",TRD:"Recording dates",TRK:"Track number/Position in set",TSI:"Size",TSS:"Software/hardware and settings used for encoding",
TT1:"Content group description",TT2:"Title/Songname/Content description",TT3:"Subtitle/Description refinement",TXT:"Lyricist/text writer",TXX:"User defined text information frame",TYE:"Year",UFI:"Unique file identifier",ULT:"Unsychronized lyric/text transcription",WAF:"Official audio file webpage",WAR:"Official artist/performer webpage",WAS:"Official audio source webpage",WCM:"Commercial information",WCP:"Copyright/Legal information",WPB:"Publishers official webpage",WXX:"User defined URL link frame",
AENC:"Audio encryption",APIC:"Attached picture",ASPI:"Audio seek point index",COMM:"Comments",COMR:"Commercial frame",ENCR:"Encryption method registration",EQU2:"Equalisation (2)",EQUA:"Equalization",ETCO:"Event timing codes",GEOB:"General encapsulated object",GRID:"Group identification registration",IPLS:"Involved people list",LINK:"Linked information",MCDI:"Music CD identifier",MLLT:"MPEG location lookup table",OWNE:"Ownership frame",PRIV:"Private frame",PCNT:"Play counter",POPM:"Popularimeter",
POSS:"Position synchronisation frame",RBUF:"Recommended buffer size",RVA2:"Relative volume adjustment (2)",RVAD:"Relative volume adjustment",RVRB:"Reverb",SEEK:"Seek frame",SYLT:"Synchronized lyric/text",SYTC:"Synchronized tempo codes",TALB:"Album/Movie/Show title",TBPM:"BPM (beats per minute)",TCOM:"Composer",TCON:"Content type",TCOP:"Copyright message",TDAT:"Date",TDLY:"Playlist delay",TDRC:"Recording time",TDRL:"Release time",TDTG:"Tagging time",TENC:"Encoded by",TEXT:"Lyricist/Text writer",TFLT:"File type",
TIME:"Time",TIPL:"Involved people list",TIT1:"Content group description",TIT2:"Title/songname/content description",TIT3:"Subtitle/Description refinement",TKEY:"Initial key",TLAN:"Language(s)",TLEN:"Length",TMCL:"Musician credits list",TMED:"Media type",TMOO:"Mood",TOAL:"Original album/movie/show title",TOFN:"Original filename",TOLY:"Original lyricist(s)/text writer(s)",TOPE:"Original artist(s)/performer(s)",TORY:"Original release year",TOWN:"File owner/licensee",TPE1:"Lead performer(s)/Soloist(s)",
TPE2:"Band/orchestra/accompaniment",TPE3:"Conductor/performer refinement",TPE4:"Interpreted, remixed, or otherwise modified by",TPOS:"Part of a set",TPRO:"Produced notice",TPUB:"Publisher",TRCK:"Track number/Position in set",TRDA:"Recording dates",TRSN:"Internet radio station name",TRSO:"Internet radio station owner",TSOA:"Album sort order",TSOP:"Performer sort order",TSOT:"Title sort order",TSIZ:"Size",TSRC:"ISRC (international standard recording code)",TSSE:"Software/Hardware and settings used for encoding",
TSST:"Set subtitle",TYER:"Year",TXXX:"User defined text information frame",UFID:"Unique file identifier",USER:"Terms of use",USLT:"Unsychronized lyric/text transcription",WCOM:"Commercial information",WCOP:"Copyright/Legal information",WOAF:"Official audio file webpage",WOAR:"Official artist/performer webpage",WOAS:"Official audio source webpage",WORS:"Official internet radio station homepage",WPAY:"Payment",WPUB:"Publishers official webpage",WXXX:"User defined URL link frame"},a={title:["TIT2","TT2"],
artist:["TPE1","TP1"],album:["TALB","TAL"],year:["TYER","TYE"],comment:["COMM","COM"],track:["TRCK","TRK"],genre:["TCON","TCO"],picture:["APIC","PIC"],lyrics:["USLT","ULT"]};h.exports=f},{"./ArrayFileReader":3,"./ID3v2FrameReader":7,"./MediaFileReader":10,"./MediaTagReader":11}],9:[function(f,h,m){function k(a,b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,
enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}var l=function(){function a(a,b){for(var c=0;c<b.length;c++){var d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(b,c,d){c&&a(b.prototype,c);d&&a(b,d);return b}}();m=f("./MediaTagReader");f("./MediaFileReader");f=function(a){function e(){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function");
var a=Object.getPrototypeOf(e).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!a||"object"!==typeof a&&"function"!==typeof a?this:a}k(e,a);l(e,[{key:"_loadData",value:function(a,b){var c=this;a.loadRange([0,16],{onSuccess:function(){c._loadAtom(a,0,"",b)},onError:b.onError})}},{key:"_loadAtom",value:function(a,b,c,e){if(b>=a.getSize())e.onSuccess();else{var d=this,f=a.getLongAt(b,!0);if(0==f||isNaN(f))e.onSuccess();else{var n=
a.getStringAt(b+4,4);if(this._isContainerAtom(n)){"meta"==n&&(b+=4);var l=(c?c+".":"")+n;"moov.udta.meta.ilst"===l?a.loadRange([b,b+f],e):a.loadRange([b+8,b+8+8],{onSuccess:function(){d._loadAtom(a,b+8,l,e)},onError:e.onError})}else a.loadRange([b+f,b+f+8],{onSuccess:function(){d._loadAtom(a,b+f,c,e)},onError:e.onError})}}}},{key:"_isContainerAtom",value:function(a){return 0<=["moov","udta","meta","ilst"].indexOf(a)}},{key:"_canReadAtom",value:function(a){return"----"!==a}},{key:"_parseData",value:function(a,
c){var e={};c=this._expandShortcutTags(c);this._readAtom(e,a,0,a.getSize(),c);for(var d in b)if(b.hasOwnProperty(d)){var f=e[b[d]];f&&(e[d]="track"===d?f.data.track:f.data)}return{type:"MP4",ftyp:a.getStringAt(8,4),version:a.getLongAt(12,!0),tags:e}}},{key:"_readAtom",value:function(a,b,c,e,d,f,n){n=void 0===n?"":n+" ";for(var l=c;l<c+e;){var h=b.getLongAt(l,!0);if(0==h)break;var k=b.getStringAt(l+4,4);if(this._isContainerAtom(k)){"meta"==k&&(l+=4);this._readAtom(a,b,l+8,h-8,d,(f?f+".":"")+k,n);
break}(!d||0<=d.indexOf(k))&&"moov.udta.meta.ilst"===f&&this._canReadAtom(k)&&(a[k]=this._readMetadataAtom(b,l));l+=h}}},{key:"_readMetadataAtom",value:function(a,b){var e=a.getLongAt(b,!0),f=a.getStringAt(b+4,4),l=a.getInteger24At(b+16+1,!0),l=d[l],k;if("trkn"==f)k={track:a.getByteAt(b+16+11),total:a.getByteAt(b+16+13)};else{var n=b+24,h=e-24;"covr"===f&&"uint8"===l&&(l="jpeg");switch(l){case "text":k=a.getStringWithCharsetAt(n,h,"utf-8").toString();break;case "uint8":k=a.getShortAt(n,!1);break;
case "int":case "uint":k=("int"==l?1==h?a.getSByteAt:2==h?a.getSShortAt:4==h?a.getSLongAt:a.getLongAt:1==h?a.getByteAt:2==h?a.getShortAt:a.getLongAt).call(a,n+(8==h?4:0),!0);break;case "jpeg":case "png":k={format:"image/"+l,data:a.getBytesAt(n,h)}}}return{id:f,size:e,description:c[f]||"Unknown",data:k}}},{key:"getShortcuts",value:function(){return b}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:0,length:16}}},{key:"canReadTagFormat",value:function(a){return"ftyp"===String.fromCharCode.apply(String,
a.slice(4,8))}}]);return e}(m);var d={0:"uint8",1:"text",13:"jpeg",14:"png",21:"int",22:"uint"},c={"\u00a9alb":"Album","\u00a9ART":"Artist",aART:"Album Artist","\u00a9day":"Release Date","\u00a9nam":"Title","\u00a9gen":"Genre",gnre:"Genre",trkn:"Track Number","\u00a9wrt":"Composer","\u00a9too":"Encoding Tool","\u00a9enc":"Encoded By",cprt:"Copyright",covr:"Cover Art","\u00a9grp":"Grouping",keyw:"Keywords","\u00a9lyr":"Lyrics","\u00a9cmt":"Comment",tmpo:"Tempo",cpil:"Compilation",disk:"Disc Number",
tvsh:"TV Show Name",tven:"TV Episode ID",tvsn:"TV Season",tves:"TV Episode",tvnn:"TV Network",desc:"Description",ldes:"Long Description",sonm:"Sort Name",soar:"Sort Artist",soaa:"Sort Album",soco:"Sort Composer",sosn:"Sort Show",purd:"Purchase Date",pcst:"Podcast",purl:"Podcast URL",catg:"Category",hdvd:"HD Video",stik:"Media Type",rtng:"Content Rating",pgap:"Gapless Playback",apID:"Purchase Account",sfID:"Country Code",atID:"Artist ID",cnID:"Catalog ID",plID:"Collection ID",geID:"Genre ID","xid ":"Vendor Information",
flvr:"Codec Flavor"},b={title:"\u00a9nam",artist:"\u00a9ART",album:"\u00a9alb",year:"\u00a9day",comment:"\u00a9cmt",track:"trkn",genre:"\u00a9gen",picture:"covr",lyrics:"\u00a9lyr"};h.exports=f},{"./MediaFileReader":10,"./MediaTagReader":11}],10:[function(f,h,m){var k=function(){function d(c,b){for(var a=0;a<b.length;a++){var e=b[a];e.enumerable=e.enumerable||!1;e.configurable=!0;"value"in e&&(e.writable=!0);Object.defineProperty(c,e.key,e)}}return function(c,b,a){b&&d(c.prototype,b);a&&d(c,a);return c}}(),
l=f("./StringUtils");f=function(){function d(){if(!(this instanceof d))throw new TypeError("Cannot call a class as a function");this._isInitialized=!1;this._size=0}k(d,[{key:"init",value:function(c){var b=this;if(this._isInitialized)setTimeout(c.onSuccess,1);else return this._init({onSuccess:function(){b._isInitialized=!0;c.onSuccess()},onError:c.onError})}},{key:"_init",value:function(c){throw Error("Must implement init function");}},{key:"loadRange",value:function(c,b){throw Error("Must implement loadRange function");
}},{key:"getSize",value:function(){if(!this._isInitialized)throw Error("init() must be called first.");return this._size}},{key:"getByteAt",value:function(c){throw Error("Must implement getByteAt function");}},{key:"getBytesAt",value:function(c,b){for(var a=Array(b),e=0;e<b;e++)a[e]=this.getByteAt(c+e);return a}},{key:"isBitSetAt",value:function(c,b){return 0!=(this.getByteAt(c)&1<<b)}},{key:"getSByteAt",value:function(c){c=this.getByteAt(c);return 127<c?c-256:c}},{key:"getShortAt",value:function(c,
b){var a=b?(this.getByteAt(c)<<8)+this.getByteAt(c+1):(this.getByteAt(c+1)<<8)+this.getByteAt(c);0>a&&(a+=65536);return a}},{key:"getSShortAt",value:function(c,b){var a=this.getShortAt(c,b);return 32767<a?a-65536:a}},{key:"getLongAt",value:function(c,b){var a=this.getByteAt(c),e=this.getByteAt(c+1),d=this.getByteAt(c+2),f=this.getByteAt(c+3),a=b?(((a<<8)+e<<8)+d<<8)+f:(((f<<8)+d<<8)+e<<8)+a;0>a&&(a+=4294967296);return a}},{key:"getSLongAt",value:function(c,b){var a=this.getLongAt(c,b);return 2147483647<
a?a-4294967296:a}},{key:"getInteger24At",value:function(c,b){var a=this.getByteAt(c),e=this.getByteAt(c+1),d=this.getByteAt(c+2),a=b?((a<<8)+e<<8)+d:((d<<8)+e<<8)+a;0>a&&(a+=16777216);return a}},{key:"getStringAt",value:function(c,b){for(var a=[],e=c,d=0;e<c+b;e++,d++)a[d]=String.fromCharCode(this.getByteAt(e));return a.join("")}},{key:"getStringWithCharsetAt",value:function(c,b,a){c=this.getBytesAt(c,b);switch((a||"").toLowerCase()){case "utf-16":case "utf-16le":case "utf-16be":a=l.readUTF16String(c,
"utf-16be"===a);break;case "utf-8":a=l.readUTF8String(c);break;default:a=l.readNullTerminatedString(c)}return a}},{key:"getCharAt",value:function(c){return String.fromCharCode(this.getByteAt(c))}},{key:"getSynchsafeInteger32At",value:function(c){var b=this.getByteAt(c),a=this.getByteAt(c+1),e=this.getByteAt(c+2);return this.getByteAt(c+3)&127|(e&127)<<7|(a&127)<<14|(b&127)<<21}}],[{key:"canReadFile",value:function(c){throw Error("Must implement canReadFile function");}}]);return d}();h.exports=f},
{"./StringUtils":12}],11:[function(f,h,m){var k=function(){function f(d,c){for(var b=0;b<c.length;b++){var a=c[b];a.enumerable=a.enumerable||!1;a.configurable=!0;"value"in a&&(a.writable=!0);Object.defineProperty(d,a.key,a)}}return function(d,c,b){c&&f(d.prototype,c);b&&f(d,b);return d}}();f("./MediaFileReader");f=function(){function f(d){if(!(this instanceof f))throw new TypeError("Cannot call a class as a function");this._mediaFileReader=d;this._tags=null}k(f,[{key:"setTagsToRead",value:function(d){this._tags=
d;return this}},{key:"read",value:function(d){var c=this;this._mediaFileReader.init({onSuccess:function(){c._loadData(c._mediaFileReader,{onSuccess:function(){try{var b=c._parseData(c._mediaFileReader,c._tags)}catch(a){if(d.onError){d.onError({type:"parseData",info:a.message});return}}d.onSuccess(b)},onError:d.onError})},onError:d.onError})}},{key:"getShortcuts",value:function(){return{}}},{key:"_loadData",value:function(d,c){throw Error("Must implement _loadData function");}},{key:"_parseData",value:function(d,
c){throw Error("Must implement _parseData function");}},{key:"_expandShortcutTags",value:function(d){if(!d)return null;for(var c=[],b=this.getShortcuts(),a=0,e;e=d[a];a++)c=c.concat(b[e]||[e]);return c}}],[{key:"getTagIdentifierByteRange",value:function(){throw Error("Must implement");}},{key:"canReadTagFormat",value:function(d){throw Error("Must implement");}}]);return f}();h.exports=f},{"./MediaFileReader":10}],12:[function(f,h,m){var k=function(){function d(c,b){for(var a=0;a<b.length;a++){var e=
b[a];e.enumerable=e.enumerable||!1;e.configurable=!0;"value"in e&&(e.writable=!0);Object.defineProperty(c,e.key,e)}}return function(c,b,a){b&&d(c.prototype,b);a&&d(c,a);return c}}(),l=function(){function d(c,b){if(!(this instanceof d))throw new TypeError("Cannot call a class as a function");this._value=c;this.bytesReadCount=b;this.length=c.length}k(d,[{key:"toString",value:function(){return this._value}}]);return d}();h.exports={readUTF16String:function(d,c,b){var a=0,e=1,g=0;b=Math.min(b||d.length,
d.length);254==d[0]&&255==d[1]?(c=!0,a=2):255==d[0]&&254==d[1]&&(c=!1,a=2);c&&(e=0,g=1);c=[];for(var f=0;a<b;f++){var h=d[a+e],k=(h<<8)+d[a+g],a=a+2;if(0==k)break;else 216>h||224<=h?c[f]=String.fromCharCode(k):(h=(d[a+e]<<8)+d[a+g],a+=2,c[f]=String.fromCharCode(k,h))}return new l(c.join(""),a)},readUTF8String:function(d,c){var b=0;c=Math.min(c||d.length,d.length);239==d[0]&&187==d[1]&&191==d[2]&&(b=3);for(var a=[],e=0;b<c;e++){var g=d[b++];if(0==g)break;else if(128>g)a[e]=String.fromCharCode(g);else if(194<=
g&&224>g){var f=d[b++];a[e]=String.fromCharCode(((g&31)<<6)+(f&63))}else if(224<=g&&240>g){var f=d[b++],h=d[b++];a[e]=String.fromCharCode(((g&255)<<12)+((f&63)<<6)+(h&63))}else if(240<=g&&245>g){var f=d[b++],h=d[b++],k=d[b++],g=((g&7)<<18)+((f&63)<<12)+((h&63)<<6)+(k&63)-65536;a[e]=String.fromCharCode((g>>10)+55296,(g&1023)+56320)}}return new l(a.join(""),b)},readNullTerminatedString:function(d,c){var b=[];c=c||d.length;for(var a=0;a<c;){var e=d[a++];if(0==e)break;b[a-1]=String.fromCharCode(e)}return new l(b.join(""),
a)}}},{}],13:[function(f,h,m){function k(c,b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);c.prototype=Object.create(b&&b.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(c,b):c.__proto__=b)}var l=function(){function c(b,a){for(var c=0;c<a.length;c++){var d=a[c];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(b,
d.key,d)}}return function(b,a,e){a&&c(b.prototype,a);e&&c(b,e);return b}}(),d=f("./ChunkedFileData");m=function(c){function b(a){if(!(this instanceof b))throw new TypeError("Cannot call a class as a function");var c;c=Object.getPrototypeOf(b).call(this);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");c=!c||"object"!==typeof c&&"function"!==typeof c?this:c;c._url=a;c._fileData=new d;return c}k(b,c);l(b,[{key:"_init",value:function(a){b._config.avoidHeadRequests?
this._fetchSizeWithGetRequest(a):this._fetchSizeWithHeadRequest(a)}},{key:"_fetchSizeWithHeadRequest",value:function(a){var b=this;this._makeXHRRequest("HEAD",null,{onSuccess:function(c){(c=b._parseContentLength(c))?(b._size=c,a.onSuccess()):b._fetchSizeWithGetRequest(a)},onError:a.onError})}},{key:"_fetchSizeWithGetRequest",value:function(a){var b=this,c=this._roundRangeToChunkMultiple([0,0]);this._makeXHRRequest("GET",c,{onSuccess:function(c){var d=b._parseContentRange(c);c=b._getXhrResponseContent(c);
if(d){if(null==d.instanceLength){b._fetchEntireFile(a);return}b._size=d.instanceLength}else b._size=c.length;b._fileData.addData(0,c);a.onSuccess()},onError:a.onError})}},{key:"_fetchEntireFile",value:function(a){var b=this;this._makeXHRRequest("GET",null,{onSuccess:function(c){c=b._getXhrResponseContent(c);b._size=c.length;b._fileData.addData(0,c);a.onSuccess()},onError:a.onError})}},{key:"_getXhrResponseContent",value:function(a){return a.responseBody||a.responseText||""}},{key:"_parseContentLength",
value:function(a){a=this._getResponseHeader(a,"Content-Length");return null==a?a:parseInt(a,10)}},{key:"_parseContentRange",value:function(a){if(a=this._getResponseHeader(a,"Content-Range")){var b=a.match(/bytes (\d+)-(\d+)\/(?:(\d+)|\*)/i);if(!b)throw Error("FIXME: Unknown Content-Range syntax: ",a);return{firstBytePosition:parseInt(b[1],10),lastBytePosition:parseInt(b[2],10),instanceLength:b[3]?parseInt(b[3],10):null}}return null}},{key:"loadRange",value:function(a,b){var c=this;c._fileData.hasDataRange(a[0],
Math.min(c._size,a[1]))?setTimeout(b.onSuccess,1):(a=this._roundRangeToChunkMultiple(a),a[1]=Math.min(c._size,a[1]),this._makeXHRRequest("GET",a,{onSuccess:function(d){d=c._getXhrResponseContent(d);c._fileData.addData(a[0],d);b.onSuccess()},onError:b.onError}))}},{key:"_roundRangeToChunkMultiple",value:function(a){return[a[0],a[0]+1024*Math.ceil((a[1]-a[0]+1)/1024)-1]}},{key:"_makeXHRRequest",value:function(a,c,d){var f=this._createXHRObject(),h=function(){if(200===f.status||206===f.status)d.onSuccess(f);
else if(d.onError)d.onError({type:"xhr",info:"Unexpected HTTP status "+f.status+".",xhr:f});f=null};"undefined"!==typeof f.onload?(f.onload=h,f.onerror=function(){if(d.onError)d.onError({type:"xhr",info:"Generic XHR error, check xhr object.",xhr:f})}):f.onreadystatechange=function(){4===f.readyState&&h()};b._config.timeoutInSec&&(f.timeout=1E3*b._config.timeoutInSec,f.ontimeout=function(){if(d.onError)d.onError({type:"xhr",info:"Timeout after "+f.timeout/1E3+"s. Use jsmediatags.Config.setXhrTimeout to override.",
xhr:f})});f.open(a,this._url);f.overrideMimeType("text/plain; charset=x-user-defined");c&&this._setRequestHeader(f,"Range","bytes="+c[0]+"-"+c[1]);this._setRequestHeader(f,"If-Modified-Since","Sat, 01 Jan 1970 00:00:00 GMT");f.send(null)}},{key:"_setRequestHeader",value:function(a,c,d){0>b._config.disallowedXhrHeaders.indexOf(c.toLowerCase())&&a.setRequestHeader(c,d)}},{key:"_hasResponseHeader",value:function(a,b){var c=a.getAllResponseHeaders();if(!c)return!1;for(var c=c.split("\r\n"),d=[],f=0;f<
c.length;f++)d[f]=c[f].split(":")[0].toLowerCase();return 0<=d.indexOf(b.toLowerCase())}},{key:"_getResponseHeader",value:function(a,b){return this._hasResponseHeader(a,b)?a.getResponseHeader(b):null}},{key:"getByteAt",value:function(a){return this._fileData.getByteAt(a).charCodeAt(0)&255}},{key:"_createXHRObject",value:function(){if("undefined"===typeof window)return new (f("xhr2").XMLHttpRequest);if(window.XMLHttpRequest)return new window.XMLHttpRequest;throw Error("XMLHttpRequest is not supported");
}}],[{key:"canReadFile",value:function(a){return"string"===typeof a&&/^[a-z]+:\/\//i.test(a)}},{key:"setConfig",value:function(a){for(var b in a)a.hasOwnProperty(b)&&(this._config[b]=a[b]);a=this._config.disallowedXhrHeaders;for(b=0;b<a.length;b++)a[b]=a[b].toLowerCase()}}]);return b}(f("./MediaFileReader"));m._config={avoidHeadRequests:!1,disallowedXhrHeaders:[],timeoutInSec:30};h.exports=m},{"./ChunkedFileData":5,"./MediaFileReader":10,xhr2:2}],14:[function(f,h,m){function k(a,b){if(!(a instanceof
b))throw new TypeError("Cannot call a class as a function");}function l(a,b){var c=0>a.offset&&(-a.offset>b||0<a.offset+a.length);return!(0<=a.offset&&a.offset+a.length>=b||c)}var d=function(){function a(b,c){for(var d=0;d<c.length;d++){var e=c[d];e.enumerable=e.enumerable||!1;e.configurable=!0;"value"in e&&(e.writable=!0);Object.defineProperty(b,e.key,e)}}return function(b,c,d){c&&a(b.prototype,c);d&&a(b,d);return b}}();f("./MediaFileReader");m=f("./NodeFileReader");var c=f("./XhrFileReader"),b=
f("./BlobFileReader"),a=f("./ArrayFileReader");f("./MediaTagReader");var e=f("./ID3v1TagReader"),g=f("./ID3v2TagReader");f=f("./MP4TagReader");var p=[],q=[],r=function(){function a(b){k(this,a);this._file=b}d(a,[{key:"setTagsToRead",value:function(a){this._tagsToRead=a;return this}},{key:"setFileReader",value:function(a){this._fileReader=a;return this}},{key:"setTagReader",value:function(a){this._tagReader=a;return this}},{key:"read",value:function(a){var b=new (this._getFileReader())(this._file),
c=this;b.init({onSuccess:function(){c._getTagReader(b,{onSuccess:function(d){(new d(b)).setTagsToRead(c._tagsToRead).read(a)},onError:a.onError})},onError:a.onError})}},{key:"_getFileReader",value:function(){return this._fileReader?this._fileReader:this._findFileReader()}},{key:"_findFileReader",value:function(){for(var a=0;a<p.length;a++)if(p[a].canReadFile(this._file))return p[a];throw Error("No suitable file reader found for ",this._file);}},{key:"_getTagReader",value:function(a,b){if(this._tagReader){var c=
this._tagReader;setTimeout(function(){b.onSuccess(c)},1)}else this._findTagReader(a,b)}},{key:"_findTagReader",value:function(a,b){for(var c=[],d=[],e=a.getSize(),f=0;f<q.length;f++){var g=q[f].getTagIdentifierByteRange();l(g,e)&&(0<=g.offset&&g.offset<e/2||0>g.offset&&g.offset<-e/2?c.push(q[f]):d.push(q[f]))}var h=!1,f={onSuccess:function(){if(h){for(var c=0;c<q.length;c++){var d=q[c].getTagIdentifierByteRange();if(l(d,e)){try{var f=a.getBytesAt(0<=d.offset?d.offset:d.offset+e,d.length)}catch(g){if(b.onError){b.onError({type:"fileReader",
info:g.message});return}}if(q[c].canReadTagFormat(f)){b.onSuccess(q[c]);return}}}if(b.onError)b.onError({type:"tagFormat",info:"No suitable tag reader found"})}else h=!0},onError:b.onError};this._loadTagIdentifierRanges(a,c,f);this._loadTagIdentifierRanges(a,d,f)}},{key:"_loadTagIdentifierRanges",value:function(a,b,c){if(0===b.length)setTimeout(c.onSuccess,1);else{for(var d=[Number.MAX_VALUE,0],e=a.getSize(),f=0;f<b.length;f++){var g=b[f].getTagIdentifierByteRange(),h=0<=g.offset?g.offset:g.offset+
e,g=h+g.length-1;d[0]=Math.min(h,d[0]);d[1]=Math.max(g,d[1])}a.loadRange(d,c)}}}]);return a}(),w=function(){function a(){k(this,a)}d(a,null,[{key:"addFileReader",value:function(b){p.push(b);return a}},{key:"addTagReader",value:function(b){q.push(b);return a}},{key:"removeTagReader",value:function(b){b=q.indexOf(b);0<=b&&q.splice(b,1);return a}},{key:"EXPERIMENTAL_avoidHeadRequests",value:function(){c.setConfig({avoidHeadRequests:!0})}},{key:"setDisallowedXhrHeaders",value:function(a){c.setConfig({disallowedXhrHeaders:a})}},
{key:"setXhrTimeoutInSec",value:function(a){c.setConfig({timeoutInSec:a})}}]);return a}();w.addFileReader(c).addFileReader(b).addFileReader(a).addTagReader(g).addTagReader(e).addTagReader(f);"undefined"!==typeof process&&w.addFileReader(m);h.exports={read:function(a,b){(new r(a)).read(b)},Reader:r,Config:w}},{"./ArrayFileReader":3,"./BlobFileReader":4,"./ID3v1TagReader":6,"./ID3v2TagReader":8,"./MP4TagReader":9,"./MediaFileReader":10,"./MediaTagReader":11,"./NodeFileReader":1,"./XhrFileReader":13}]},
{},[14])(14)});

View File

@ -30,32 +30,20 @@ class MP3FileHandler extends DataHandlerExtension {
* @return Image|null * @return Image|null
*/ */
protected function create_image_from_data($filename, $metadata) { protected function create_image_from_data($filename, $metadata) {
global $config;
$image = new Image(); $image = new Image();
// FIXME: need more flash format specs :| //NOTE: No need to set width/height as we don't use it.
$image->width = 0; $image->width = 1;
$image->height = 0; $image->height = 1;
$image->filesize = $metadata['size']; $image->filesize = $metadata['size'];
$image->hash = $metadata['hash']; $image->hash = $metadata['hash'];
//Cheat by using the filename to store artist/title if available //Filename is renamed to "artist - title.mp3" when the user requests download by using the download attribute & jsmediatags.js
require_once('lib/getid3/getid3/getid3.php'); $image->filename = $metadata['filename'];
$getID3 = new getID3;
$ThisFileInfo = $getID3->analyze($filename, TRUE);
if (isset($ThisFileInfo['tags']['id3v2']['artist'][0]) && isset($ThisFileInfo['tags']['id3v2']['title'][0])) {
$image->filename = $ThisFileInfo['tags']['id3v2']['artist'][0]." - ".$ThisFileInfo['tags']['id3v2']['title'][0].".mp3";
} else if (isset($ThisFileInfo['tags']['id3v1']['artist'][0]) && isset($ThisFileInfo['tags']['id3v1']['title'][0])) {
$image->filename = $ThisFileInfo['tags']['id3v1']['artist'][0]." - ".$ThisFileInfo['tags']['id3v1']['title'][0].".mp3";
} else {
$image->filename = $metadata['filename'];
}
$image->ext = $metadata['extension']; $image->ext = $metadata['extension'];
$image->tag_array = Tag::explode($metadata['tags']); $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
$image->source = $metadata['source']; $image->source = $metadata['source'];
return $image; return $image;
@ -66,15 +54,15 @@ class MP3FileHandler extends DataHandlerExtension {
* @return bool * @return bool
*/ */
protected function check_contents($file) { protected function check_contents($file) {
$success = FALSE;
if (file_exists($file)) { if (file_exists($file)) {
require_once('lib/getid3/getid3/getid3.php'); $mimeType = mime_content_type($file);
$getID3 = new getID3;
$ThisFileInfo = $getID3->analyze($file, TRUE); $success = ($mimeType == 'audio/mpeg');
if (isset($ThisFileInfo['fileformat']) && $ThisFileInfo['fileformat'] == "mp3") {
return TRUE;
}
} }
return FALSE;
return $success;
} }
} }

14
ext/handle_mp3/style.css Normal file
View File

@ -0,0 +1,14 @@
.audio_image {
min-width: 300px;
width: 65%;
}
/* Hide download button as we use our own */
audio::-internal-media-controls-download-button {
display:none;
}
audio::-webkit-media-controls-enclosure {
overflow:hidden;
}
audio::-webkit-media-controls-panel {
width: calc(100% + 30px); /* Adjust as needed */
}

View File

@ -6,17 +6,35 @@ class MP3FileHandlerTheme extends Themelet {
$ilink = $image->get_image_link(); $ilink = $image->get_image_link();
$fname = url_escape($image->filename); //Most of the time this will be the title/artist of the song. $fname = url_escape($image->filename); //Most of the time this will be the title/artist of the song.
$html = " $html = "
<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' <audio controls class='shm-main-image audio_image' id='main_image' alt='main image'>
codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0' <source src=\"$ilink\" type=\"audio/mpeg\">
width='400' height='15'> Your browser does not support the audio element.
<param name='movie' value='$data_href/ext/handle_mp3/xspf_player_slim.swf?song_url=$ilink'/> </audio>
<param name='quality' value='high' /> <p>Title: <span id='audio-title'>???</span> | Artist: <span id='audio-artist'>???</span></p>
<embed src='$data_href/ext/handle_mp3/xspf_player_slim.swf?song_url=$ilink&song_title=$fname' quality='high'
pluginspage='http://www.macromedia.com/go/getflashplayer' <script>
width='400' height='15' $('#main_image').prop('volume', 0.25);
type='application/x-shockwave-flash'></embed>
</object> var jsmediatags = window.jsmediatags;
<p><a href='$ilink'>Download</a>"; jsmediatags.read(location.origin+base_href+'$ilink', {
onSuccess: function(tag) {
var artist = tag.tags.artist,
title = tag.tags.title;
$('#audio-title').text(title);
$('#audio-artist').text(artist);
$('#audio-download').prop('download', (artist+' - '+title).substr(0, 250)+'.mp3');
},
onError: function(error) {
console.log(error);
}
});
</script>
<p><a href='$ilink' id='audio-download'>Download</a>";
$page->add_html_header("<script src='{$data_href}/ext/handle_mp3/lib/jsmediatags.min.js' type='text/javascript'></script>");
$page->add_block(new Block("Music", $html, "main", 10)); $page->add_block(new Block("Music", $html, "main", 10));
} }
} }

View File

@ -1,10 +0,0 @@
Copyright (c) 2005, Fabricio Zuardi
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Binary file not shown.

View File

@ -32,11 +32,11 @@ class PixelFileHandler extends DataHandlerExtension {
$image->height = $info[1]; $image->height = $info[1];
$image->filesize = $metadata['size']; $image->filesize = $metadata['size'];
$image->hash = $metadata['hash']; $image->hash = $metadata['hash'];
$image->filename = (($pos = strpos($metadata['filename'],'?')) !== false) ? substr($metadata['filename'],0,$pos) : $metadata['filename']; $image->filename = (($pos = strpos($metadata['filename'],'?')) !== false) ? substr($metadata['filename'],0,$pos) : $metadata['filename'];
$image->ext = (($pos = strpos($metadata['extension'],'?')) !== false) ? substr($metadata['extension'],0,$pos) : $metadata['extension']; $image->ext = (($pos = strpos($metadata['extension'],'?')) !== false) ? substr($metadata['extension'],0,$pos) : $metadata['extension'];
$image->tag_array = Tag::explode($metadata['tags']); $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
$image->source = $metadata['source']; $image->source = $metadata['source'];
return $image; return $image;
} }

View File

@ -1,5 +1,7 @@
$(function() { $(function() {
function zoom(zoom_type) { function zoom(zoom_type, save_cookie) {
save_cookie = save_cookie === undefined ? true : save_cookie;
var img = $('.shm-main-image'); var img = $('.shm-main-image');
if(zoom_type == "full") { if(zoom_type == "full") {
@ -21,21 +23,28 @@ $(function() {
$(".shm-zoomer").val(zoom_type); $(".shm-zoomer").val(zoom_type);
$.cookie("ui-image-zoom", zoom_type, {path: '/', expires: 365}); if (save_cookie) {
Cookies.set("ui-image-zoom", zoom_type, {expires: 365});
}
} }
$(".shm-zoomer").change(function(e) { $(".shm-zoomer").change(function(e) {
zoom(this.options[this.selectedIndex].value); zoom(this.options[this.selectedIndex].value);
}); });
$(window).resize(function(e) {
$(".shm-zoomer").each(function (e) {
zoom(this.options[this.selectedIndex].value, false)
});
});
$(".shm-main-image").click(function(e) { $("img.shm-main-image").click(function(e) {
switch($.cookie("ui-image-zoom")) { switch(Cookies.get("ui-image-zoom")) {
case "full": zoom("width"); break; case "full": zoom("width"); break;
default: zoom("full"); break; default: zoom("full"); break;
} }
}); });
if($.cookie("ui-image-zoom")) { if(Cookies.get("ui-image-zoom")) {
zoom($.cookie("ui-image-zoom")); zoom(Cookies.get("ui-image-zoom"));
} }
}); });

View File

@ -3,14 +3,14 @@
* Name: Handle SVG * Name: Handle SVG
* Author: Shish <webmaster@shishnet.org> * Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/ * Link: http://code.shishnet.org/shimmie2/
* Description: Handle SVG files. (No thumbnail is generated for SVG files) * Description: Handle static SVG files. (No thumbnail is generated for SVG files)
*/ */
class SVGFileHandler extends Extension { class SVGFileHandler extends Extension {
public function onDataUpload(DataUploadEvent $event) { public function onDataUpload(DataUploadEvent $event) {
if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) { if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
$hash = $event->hash; $hash = $event->hash;
if(!move_upload_to_archive($event)) return; move_upload_to_archive($event);
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data(warehouse_path("images", $hash), $event->metadata); $image = $this->create_image_from_data(warehouse_path("images", $hash), $event->metadata);
if(is_null($image)) { if(is_null($image)) {
@ -75,7 +75,7 @@ class SVGFileHandler extends Extension {
$image->hash = $metadata['hash']; $image->hash = $metadata['hash'];
$image->filename = $metadata['filename']; $image->filename = $metadata['filename'];
$image->ext = $metadata['extension']; $image->ext = $metadata['extension'];
$image->tag_array = Tag::explode($metadata['tags']); $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
$image->source = $metadata['source']; $image->source = $metadata['source'];
return $image; return $image;
@ -101,6 +101,10 @@ class MiniSVGParser {
/** @var int */ /** @var int */
public $height=0; public $height=0;
/** @var int */
private $xml_depth=0;
/** @param string $file */
function __construct($file) { function __construct($file) {
$xml_parser = xml_parser_create(); $xml_parser = xml_parser_create();
xml_set_element_handler($xml_parser, array($this, "startElement"), array($this, "endElement")); xml_set_element_handler($xml_parser, array($this, "startElement"), array($this, "endElement"));
@ -109,13 +113,15 @@ class MiniSVGParser {
} }
function startElement($parser, $name, $attrs) { function startElement($parser, $name, $attrs) {
if($name == "SVG") { if($name == "SVG" && $this->xml_depth == 0) {
$this->width = int_escape($attrs["WIDTH"]); $this->width = int_escape($attrs["WIDTH"]);
$this->height = int_escape($attrs["HEIGHT"]); $this->height = int_escape($attrs["HEIGHT"]);
} }
$this->xml_depth++;
} }
function endElement($parser, $name) { function endElement($parser, $name) {
$this->xml_depth--;
} }
} }

View File

@ -5,9 +5,7 @@ class SVGFileHandlerTheme extends Themelet {
$ilink = make_link("get_svg/{$image->id}/{$image->id}.svg"); $ilink = make_link("get_svg/{$image->id}/{$image->id}.svg");
// $ilink = $image->get_image_link(); // $ilink = $image->get_image_link();
$html = " $html = "
<object data='$ilink' type='image/svg+xml' width='{$image->width}' height='{$image->height}'> <img src='$ilink' id='main_image' class='shm-main-image' data-width='{$image->width}' data-height='{$image->height}' />
<embed src='$ilink' type='image/svg+xml' width='{$image->width}' height='{$image->height}' />
</object>
"; ";
$page->add_block(new Block("Image", $html, "main", 10)); $page->add_block(new Block("Image", $html, "main", 10));
} }

View File

@ -2,7 +2,7 @@
/* /*
* Name: Handle Video * Name: Handle Video
* Author: velocity37 <velocity37@gmail.com> * Author: velocity37 <velocity37@gmail.com>
* Modified By: Shish <webmaster@shishnet.org>, jgen <jeffgenovy@gmail.com> * Modified By: Shish <webmaster@shishnet.org>, jgen <jeffgenovy@gmail.com>, im-mi <im.mi.mail.mi@gmail.com>
* License: GPLv2 * License: GPLv2
* Description: Handle FLV, MP4, OGV and WEBM video files. * Description: Handle FLV, MP4, OGV and WEBM video files.
* Documentation: * Documentation:
@ -12,31 +12,46 @@
* OGV, WEBM: HTML5<br> * OGV, WEBM: HTML5<br>
* MP4's flash fallback is forced with a bit of Javascript as some browsers won't fallback if they can't play H.264. * MP4's flash fallback is forced with a bit of Javascript as some browsers won't fallback if they can't play H.264.
* In the future, it may be necessary to change the user agent checks to reflect the current state of H.264 support.<br><br> * In the future, it may be necessary to change the user agent checks to reflect the current state of H.264 support.<br><br>
* Made possible by:<br>
* <a href='http://getid3.sourceforge.net/'>getID3()</a> - Gets media information with PHP (no bulky FFMPEG API required).<br>
* <a href='http://jarisflvplayer.org/'>Jaris FLV Player</a> - GPLv3 flash multimedia player.
*/ */
class VideoFileHandler extends DataHandlerExtension { class VideoFileHandler extends DataHandlerExtension {
public function onInitExt(InitExtEvent $event) { public function onInitExt(InitExtEvent $event) {
global $config; global $config;
$config->set_default_string('video_thumb_engine', 'static');
$config->set_default_string('thumb_ffmpeg_path', '');
// By default we generate thumbnails ignoring the aspect ratio of the video file. if($config->get_int("ext_handle_video_version") < 1) {
// if($ffmpeg = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffmpeg')) {
// Why? - This allows Shimmie to work with older versions of FFmpeg by default, //ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static
// rather than completely failing out of the box. If people complain that their if(is_executable(strtok($ffmpeg, PHP_EOL))) {
// thumbnails are distorted, then they can turn this feature on manually later. $config->set_default_string('video_thumb_engine', 'ffmpeg');
$config->set_default_bool('video_thumb_ignore_aspect_ratio', true); $config->set_default_string('thumb_ffmpeg_path', 'ffmpeg');
}
} else {
$config->set_default_string('video_thumb_engine', 'static');
$config->set_default_string('thumb_ffmpeg_path', '');
}
// By default we generate thumbnails ignoring the aspect ratio of the video file.
//
// Why? - This allows Shimmie to work with older versions of FFmpeg by default,
// rather than completely failing out of the box. If people complain that their
// thumbnails are distorted, then they can turn this feature on manually later.
$config->set_default_bool('video_thumb_ignore_aspect_ratio', TRUE);
$config->set_int("ext_handle_video_version", 1);
log_info("handle_video", "extension installed");
}
$config->set_default_bool('video_playback_autoplay', TRUE);
$config->set_default_bool('video_playback_loop', TRUE);
} }
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
//global $config; //global $config;
$thumbers = array(); $thumbers = array(
$thumbers['None'] = "static"; 'None' => 'static',
$thumbers['ffmpeg'] = "ffmpeg"; 'ffmpeg' => 'ffmpeg'
);
$sb = new SetupBlock("Video Thumbnail Options"); $sb = new SetupBlock("Video Thumbnail Options");
@ -53,6 +68,12 @@ class VideoFileHandler extends DataHandlerExtension {
$sb->add_bool_option("video_thumb_ignore_aspect_ratio", "Ignore aspect ratio when creating thumbnails: "); $sb->add_bool_option("video_thumb_ignore_aspect_ratio", "Ignore aspect ratio when creating thumbnails: ");
$event->panel->add_block($sb); $event->panel->add_block($sb);
$sb = new SetupBlock("Video Playback Options");
$sb->add_bool_option("video_playback_autoplay", "Autoplay: ");
$sb->add_label("<br>");
$sb->add_bool_option("video_playback_loop", "Loop: ");
$event->panel->add_block($sb);
} }
/** /**
@ -85,12 +106,12 @@ class VideoFileHandler extends DataHandlerExtension {
if ($config->get_bool("video_thumb_ignore_aspect_ratio") == true) if ($config->get_bool("video_thumb_ignore_aspect_ratio") == true)
{ {
$cmd = escapeshellcmd("{$ffmpeg} -i {$inname} -ss 00:00:00.0 -f image2 -vframes 1 {$outname}"); $cmd = escapeshellcmd("{$ffmpeg} -y -i {$inname} -ss 00:00:00.0 -f image2 -vframes 1 {$outname}");
} }
else else
{ {
$scale = 'scale="' . escapeshellarg("if(gt(a,{$w}/{$h}),{$w},-1)") . ':' . escapeshellarg("if(gt(a,{$w}/{$h}),-1,{$h})") . '"'; $scale = 'scale="' . escapeshellarg("if(gt(a,{$w}/{$h}),{$w},-1)") . ':' . escapeshellarg("if(gt(a,{$w}/{$h}),-1,{$h})") . '"';
$cmd = "{$ffmpeg} -i {$inname} -vf {$scale} -ss 00:00:00.0 -f image2 -vframes 1 {$outname}"; $cmd = "{$ffmpeg} -y -i {$inname} -vf {$scale} -ss 00:00:00.0 -f image2 -vframes 1 {$outname}";
} }
exec($cmd, $output, $returnValue); exec($cmd, $output, $returnValue);
@ -119,34 +140,28 @@ class VideoFileHandler extends DataHandlerExtension {
/** /**
* @param string $filename * @param string $filename
* @param mixed[] $metadata * @param mixed[] $metadata
* @return Image|null * @return Image
*/ */
protected function create_image_from_data($filename, $metadata) { protected function create_image_from_data($filename, $metadata) {
$image = new Image(); $image = new Image();
require_once('lib/getid3/getid3/getid3.php'); //NOTE: No need to set width/height as we don't use it.
$getID3 = new getID3; $image->width = 1;
$ThisFileInfo = $getID3->analyze($filename); $image->height = 1;
if (isset($ThisFileInfo['video']['resolution_x']) && isset($ThisFileInfo['video']['resolution_y'])) { switch (mime_content_type($filename)) {
$image->width = $ThisFileInfo['video']['resolution_x'];
$image->height = $ThisFileInfo['video']['resolution_y'];
} else {
$image->width = 0;
$image->height = 0;
}
switch ($ThisFileInfo['mime_type']) {
case "video/webm": case "video/webm":
$image->ext = "webm"; $image->ext = "webm";
break; break;
case "video/quicktime": case "video/mp4":
$image->ext = "mp4"; $image->ext = "mp4";
break; break;
case "application/ogg": case "video/ogg":
$image->ext = "ogv"; $image->ext = "ogv";
break; break;
case "video/flv":
$image->ext = "flv";
break;
case "video/x-flv": case "video/x-flv":
$image->ext = "flv"; $image->ext = "flv";
break; break;
@ -155,7 +170,7 @@ class VideoFileHandler extends DataHandlerExtension {
$image->filesize = $metadata['size']; $image->filesize = $metadata['size'];
$image->hash = $metadata['hash']; $image->hash = $metadata['hash'];
$image->filename = $metadata['filename']; $image->filename = $metadata['filename'];
$image->tag_array = Tag::explode($metadata['tags']); $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
$image->source = $metadata['source']; $image->source = $metadata['source'];
return $image; return $image;
@ -166,20 +181,20 @@ class VideoFileHandler extends DataHandlerExtension {
* @return bool * @return bool
*/ */
protected function check_contents($file) { protected function check_contents($file) {
$success = FALSE;
if (file_exists($file)) { if (file_exists($file)) {
require_once('lib/getid3/getid3/getid3.php'); $mimeType = mime_content_type($file);
$getID3 = new getID3;
$ThisFileInfo = $getID3->analyze($file); $success = in_array($mimeType, [
if (isset($ThisFileInfo['mime_type']) && ( 'video/webm',
$ThisFileInfo['mime_type'] == "video/webm" || 'video/mp4',
$ThisFileInfo['mime_type'] == "video/quicktime" || 'video/ogg',
$ThisFileInfo['mime_type'] == "application/ogg" || 'video/flv',
$ThisFileInfo['mime_type'] == 'video/x-flv') 'video/x-flv'
) { ]);
return TRUE;
}
} }
return FALSE;
return $success;
} }
} }

View File

@ -2,47 +2,62 @@
class VideoFileHandlerTheme extends Themelet { class VideoFileHandlerTheme extends Themelet {
public function display_image(Page $page, Image $image) { public function display_image(Page $page, Image $image) {
$data_href = get_base_href(); global $config;
$ilink = $image->get_image_link(); $ilink = $image->get_image_link();
$thumb_url = make_http($image->get_thumb_link()); //used as fallback image
$ext = strtolower($image->get_ext()); $ext = strtolower($image->get_ext());
$full_url = make_http($ilink);
$autoplay = $config->get_bool("video_playback_autoplay");
$loop = $config->get_bool("video_playback_loop");
$player = make_link('lib/vendor/swf/flashmediaelement.swf');
if ($ext == "mp4") { $html = "Video not playing? <a href='$ilink'>Click here</a> to download the file.<br/>";
$html = "Video not playing? <a href='" . $image->parse_link_template(make_link('image/$id/$id%20-%20$tags.$ext')) . "'>Click here</a> to download the file.<br/>
<script language='JavaScript' type='text/javascript'> //Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
if( navigator.userAgent.match(/Firefox/i) || $supportedExts = ['mp4' => 'video/mp4', 'm4v' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm', 'flv' => 'video/flv'];
navigator.userAgent.match(/Opera/i) || if(array_key_exists($ext, $supportedExts)) {
(navigator.userAgent.match(/MSIE/i) && parseFloat(navigator.appVersion.split('MSIE')[1]) < 9)){ //FLV isn't supported by <video>, but it should always fallback to the flash-based method.
document.write(\"<object data='{$data_href}/lib/Jaris/bin/JarisFLVPlayer.swf' id='VideoPlayer' type='application/x-shockwave-flash' height='" . strval($image->height + 1). "px' width='" . strval($image->width) . "px'><param value='#000000' name='bgcolor'><param name='allowFullScreen' value='true'><param value='high' name='quality'><param value='opaque' name='wmode'><param value='source=$ilink&amp;type=video&amp;streamtype=file&amp;controltype=0' name='flashvars'></object>\"); if($ext == "webm") {
} //Several browsers still lack WebM support sadly: http://caniuse.com/#feat=webm
else { $html .= "<!--[if IE]><p>To view webm files with IE, please <a href='https://tools.google.com/dlpage/webmmf/' target='_blank'>download this plugin</a>.</p><![endif]-->";
document.write(\"<video controls autoplay loop'>\"); }
document.write(\"<source src='" . make_link("/image/" . $image->id) . "' type='video/mp4' />\");
document.write(\"<object data='{$data_href}/lib/Jaris/bin/JarisFLVPlayer.swf' id='VideoPlayer' type='application/x-shockwave-flash' height='" . strval($image->height + 1). "px' width='" . strval($image->width) . "px'><param value='#000000' name='bgcolor'><param name='allowFullScreen' value='true'><param value='high' name='quality'><param value='opaque' name='wmode'><param value='source=$ilink&amp;type=video&amp;streamtype=file&amp;controltype=0' name='flashvars'></object>\"); $html_fallback = "
} <object width=\"100%\" height=\"480px\" type=\"application/x-shockwave-flash\" data=\"$player\">
</script> <param name=\"movie\" value=\"$player\" />
<noscript>Javascript appears to be disabled. Please enable it and try again.</noscript>";
} elseif ($ext == "flv") { <param name=\"allowFullScreen\" value=\"true\" />
$html = "Video not playing? <a href='" . $image->parse_link_template(make_link('image/$id/$id%20-%20$tags.$ext')) . "'>Click here</a> to download the file.<br/> <param name=\"wmode\" value=\"opaque\" />
<object data='{$data_href}/lib/Jaris/bin/JarisFLVPlayer.swf' id='VideoPlayer' type='application/x-shockwave-flash' height='" . strval($image->height + 1). "px' width='" . strval($image->width) . "px'>
<param value='#000000' name='bgcolor'> <param name=\"flashVars\" value=\""
<param name='allowFullScreen' value='true'> . "controls=true"
<param value='high' name='quality'> . "&autoplay=" . ($autoplay ? 'true' : 'false')
<param value='opaque' name='wmode'> . "&poster={$thumb_url}"
<param value='source={$ilink}&amp;type=video&amp;streamtype=file&amp;controltype=0' name='flashvars'> . "&file={$full_url}"
</object>"; . "&loop=" . ($loop ? 'true' : 'false') . "\" />
} elseif ($ext == "ogv") { <img src=\"{$thumb_url}\" />
$html = "Video not playing? <a href='" . $image->parse_link_template(make_link('image/$id/$id%20-%20$tags.$ext')) . "'>Click here</a> to download the file.<br/> </object>";
<video controls autoplay loop>
<source src='" . make_link("/image/" . $image->id) . "' type='video/ogg' /> if($ext == "flv") {
</video>"; //FLV doesn't support <video>.
} elseif ($ext == "webm") { $html .= $html_fallback;
$ie_only = "<!--[if IE]><p>To view webm files with IE, please <a href='http://tools.google.com/dlpage/webmmf/' target='_blank'>download this plugin</a>.</p><![endif]-->"; } else {
$html = $ie_only ."Video not playing? <a href='" . $image->parse_link_template(make_link('image/$id/$id%20-%20$tags.$ext')) . "'>Click here</a> to download the file.<br/> $autoplay = ($autoplay ? ' autoplay' : '');
<video controls autoplay loop> $loop = ($loop ? ' loop' : '');
<source src='" . make_link("/image/" . $image->id) . "' type='video/webm' />
</video>"; $html .= "
} <video controls class='shm-main-image' id='main_image' alt='main image' {$autoplay} {$loop} style='max-width: 100%'>
else { <source src='{$ilink}' type='{$supportedExts[$ext]}'>
<!-- If browser doesn't support filetype, fallback to flash -->
{$html_fallback}
</video>
<script>$('#main_image').prop('volume', 0.25);</script>
";
}
} else {
//This should never happen, but just in case let's have a fallback..
$html = "Video type '$ext' not recognised"; $html = "Video type '$ext' not recognised";
} }
$page->add_block(new Block("Video", $html, "main", 10)); $page->add_block(new Block("Video", $html, "main", 10));

View File

@ -49,7 +49,7 @@ class Home extends Extension {
global $config; global $config;
$base_href = get_base_href(); $base_href = get_base_href();
$sitename = $config->get_string('title'); $sitename = $config->get_string('title');
$contact_link = $config->get_string('contact_link'); $contact_link = contact_link();
$counter_dir = $config->get_string('home_counter', 'default'); $counter_dir = $config->get_string('home_counter', 'default');
$total = Image::count_images(); $total = Image::count_images();

13
ext/home/style.css Normal file
View File

@ -0,0 +1,13 @@
div#front-page h1 {font-size: 4em; margin-top: 2em; margin-bottom: 0px; text-align: center; border: none; background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;}
div#front-page {text-align:center;}
.space {margin-bottom: 1em;}
div#front-page div#links a {margin: 0 0.5em;}
div#front-page li {list-style-type: none; margin: 0;}
@media (max-width: 800px) {
div#front-page h1 {font-size: 3em; margin-top: 0.5em; margin-bottom: 0.5em;}
#counter {display: none;}
}
div#front-page > #search > form { margin: 0 auto; }
div#front-page > #search > form > ul { width: 225px; vertical-align: middle; display: inline-block; }
div#front-page > #search > form > input[type=submit]{ padding: 4px 6px; }

View File

@ -3,10 +3,10 @@
class HomeTheme extends Themelet { class HomeTheme extends Themelet {
public function display_page(Page $page, $sitename, $base_href, $theme_name, $body) { public function display_page(Page $page, $sitename, $base_href, $theme_name, $body) {
$page->set_mode("data"); $page->set_mode("data");
$hh = "";
$page->add_auto_html_headers(); $page->add_auto_html_headers();
foreach($page->html_headers as $h) {$hh .= $h;} $hh = $page->get_all_html_headers();
$page->set_data(<<<EOD $page->set_data(<<<EOD
<!doctype html>
<html> <html>
<head> <head>
<title>$sitename</title> <title>$sitename</title>
@ -14,17 +14,6 @@ class HomeTheme extends Themelet {
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
$hh $hh
</head> </head>
<style>
div#front-page h1 {font-size: 4em; margin-top: 2em; margin-bottom: 0px; text-align: center; border: none; background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;}
div#front-page {text-align:center;}
.space {margin-bottom: 1em;}
div#front-page div#links a {margin: 0 0.5em;}
div#front-page li {list-style-type: none; margin: 0;}
@media (max-width: 800px) {
div#front-page h1 {font-size: 3em; margin-top: 0.5em; margin-bottom: 0.5em;}
#counter {display: none;}
}
</style>
<body> <body>
$body $body
</body> </body>
@ -37,7 +26,7 @@ EOD
$main_links_html = empty($main_links) ? "" : "<div class='space' id='links'>$main_links</div>"; $main_links_html = empty($main_links) ? "" : "<div class='space' id='links'>$main_links</div>";
$message_html = empty($main_text) ? "" : "<div class='space' id='message'>$main_text</div>"; $message_html = empty($main_text) ? "" : "<div class='space' id='message'>$main_text</div>";
$counter_html = empty($counter_text) ? "" : "<div class='space' id='counter'>$counter_text</div>"; $counter_html = empty($counter_text) ? "" : "<div class='space' id='counter'>$counter_text</div>";
$contact_link = empty($contact_link) ? "" : "<br><a href='mailto:$contact_link'>Contact</a> &ndash;"; $contact_link = empty($contact_link) ? "" : "<br><a href='$contact_link'>Contact</a> &ndash;";
$search_html = " $search_html = "
<div class='space' id='search'> <div class='space' id='search'>
<form action='".make_link("post/list")."' method='GET'> <form action='".make_link("post/list")."' method='GET'>

View File

@ -12,8 +12,10 @@
* An image is being added to the database. * An image is being added to the database.
*/ */
class ImageAdditionEvent extends Event { class ImageAdditionEvent extends Event {
var $user; /** @var User */
/** @var \Image */ public $user;
/** @var Image */
public $image; public $image;
/** /**
@ -30,7 +32,7 @@ class ImageAdditionEvent extends Event {
} }
class ImageAdditionException extends SCoreException { class ImageAdditionException extends SCoreException {
var $error; public $error;
/** /**
* @param string $error * @param string $error
@ -374,7 +376,7 @@ class ImageIO extends Extension {
$image->tag_array = array(); $image->tag_array = array();
send_event(new TagSetEvent($image, $tags_to_set)); send_event(new TagSetEvent($image, $tags_to_set));
if($image->source) { if($image->source !== null) {
log_info("core-image", "Source for Image #{$image->id} set to: {$image->source}"); log_info("core-image", "Source for Image #{$image->id} set to: {$image->source}");
} }
} }

View File

@ -1,5 +1,5 @@
<?php <?php
class ImageTest extends ShimmiePHPUnitTestCase { class ImageIOTest extends ShimmiePHPUnitTestCase {
public function testUserStats() { public function testUserStats() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");

View File

@ -11,7 +11,7 @@
// RemoveImageHashBanEvent {{{ // RemoveImageHashBanEvent {{{
class RemoveImageHashBanEvent extends Event { class RemoveImageHashBanEvent extends Event {
var $hash; public $hash;
/** /**
* @param string $hash * @param string $hash
@ -23,8 +23,8 @@ class RemoveImageHashBanEvent extends Event {
// }}} // }}}
// AddImageHashBanEvent {{{ // AddImageHashBanEvent {{{
class AddImageHashBanEvent extends Event { class AddImageHashBanEvent extends Event {
var $hash; public $hash;
var $reason; public $reason;
/** /**
* @param string $hash * @param string $hash

View File

@ -11,98 +11,124 @@
* This is done to prevent duplicate views. * This is done to prevent duplicate views.
* A person can only count as a view again 1 hour after viewing the image initially. * A person can only count as a view again 1 hour after viewing the image initially.
*/ */
class image_view_counter extends Extension { class ImageViewCounter extends Extension {
private $view_interval = 3600; # allows views to be added each hour private $view_interval = 3600; # allows views to be added each hour
# Add Setup Block with options for view counter # Add Setup Block with options for view counter
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Image View Counter"); $sb = new SetupBlock("Image View Counter");
$sb->add_bool_option("image_viewcounter_adminonly", "Display view counter only to admin"); $sb->add_bool_option("image_viewcounter_adminonly", "Display view counter only to admin");
$event->panel->add_block($sb); $event->panel->add_block($sb);
} }
# Adds view to database if needed # Adds view to database if needed
public function onDisplayingImage(DisplayingImageEvent $event) { public function onDisplayingImage(DisplayingImageEvent $event) {
$imgid = $event->image->id; // determines image id $imgid = $event->image->id; // determines image id
$this->addview($imgid); // adds a view $this->addview($imgid); // adds a view
} }
# display views to user or admin below image if allowed # display views to user or admin below image if allowed
public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event) { public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event) {
global $user, $config; global $user, $config;
$adminonly = $config->get_bool("image_viewcounter_adminonly"); // todo0 $adminonly = $config->get_bool("image_viewcounter_adminonly"); // todo
if ($adminonly == false || ($adminonly && $user->is_admin())) if ($adminonly == false || ($adminonly && $user->is_admin()))
$event->add_part("<tr><th>Views:</th><td>". $event->add_part(
$this->get_view_count($event->image->id) ."</th></tr>", 38); "<tr><th>Views:</th><td>".
} $this->get_view_count($event->image->id) .
"</tr>", 38);
}
# Installs DB table # Installs DB table
public function onInitExt(InitExtEvent $event) { public function onInitExt(InitExtEvent $event) {
global $database, $config; global $database, $config;
// if the sql table doesn't exist yet, create it // if the sql table doesn't exist yet, create it
if($config->get_bool("image_viewcounter_installed") == false) { //todo if($config->get_bool("image_viewcounter_installed") == false) { //todo
$database->create_table("image_views"," $database->create_table("image_views","
id SCORE_AIPK, id SCORE_AIPK,
image_id INTEGER NOT NULL, image_id INTEGER NOT NULL,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
timestamp INTEGER NOT NULL, timestamp INTEGER NOT NULL,
ipaddress SCORE_INET NOT NULL"); ipaddress SCORE_INET NOT NULL");
$config->set_bool("image_viewcounter_installed", true); $config->set_bool("image_viewcounter_installed", true);
} }
} }
# Adds a view to the item if needed /**
private function addview($imgid) * Adds a view to the item if needed
{ * @param int $imgid
global $database, $user; */
private function addview($imgid)
{
global $database, $user;
// don't add view if person already viewed recently // don't add view if person already viewed recently
if ($this->can_add_view($imgid) == false) return; if ($this->can_add_view($imgid) == false) return;
// Add view for current IP // Add view for current IP
$database->execute("INSERT INTO image_views (image_id, user_id, timestamp, ipaddress) $database->execute(
VALUES (:image_id, :user_id, :timestamp, :ipaddress)", array( "
"image_id" => $imgid, INSERT INTO image_views (image_id, user_id, timestamp, ipaddress)
"user_id" => $user->id, VALUES (:image_id, :user_id, :timestamp, :ipaddress)
"timestamp" => time(), ",
"ipaddress" => $_SERVER['REMOTE_ADDR'], array(
)); "image_id" => $imgid,
} "user_id" => $user->id,
"timestamp" => time(),
"ipaddress" => $_SERVER['REMOTE_ADDR'],
)
);
}
# Returns true if this IP hasn't recently viewed this image /**
private function can_add_view($imgid) * Returns true if this IP hasn't recently viewed this image
{ * @param int $imgid
global $database; */
private function can_add_view($imgid)
{
global $database;
// counts views from current IP in the last hour // counts views from current IP in the last hour
$recent_from_ip = (int)$database->get_one("SELECT COUNT(*) FROM image_views WHERE $recent_from_ip = (int)$database->get_one(
ipaddress=:ipaddress AND timestamp >:lasthour AND image_id =:image_id", "
array( "ipaddress" => $_SERVER['REMOTE_ADDR'], SELECT COUNT(*)
"lasthour" => time() - $this->view_interval, FROM image_views
"image_id" => $imgid)); WHERE ipaddress=:ipaddress AND timestamp >:lasthour AND image_id =:image_id
",
array(
"ipaddress" => $_SERVER['REMOTE_ADDR'],
"lasthour" => time() - $this->view_interval,
"image_id" => $imgid
)
);
// if no views were found with the set criteria, return true // if no views were found with the set criteria, return true
if($recent_from_ip == 0) return true; if($recent_from_ip == 0) return true;
else return false; else return false;
} }
# Returns the int of the view count from the given image id /**
// $imgid - if not set or 0, return views of all images * Returns the int of the view count from the given image id
private function get_view_count($imgid = 0) * @param int $imgid - if not set or 0, return views of all images
{ */
global $database; private function get_view_count($imgid = 0)
{
global $database;
if ($imgid == 0) // return view count of all images if ($imgid == 0) // return view count of all images
$view_count = (int)$database->get_one("SELECT COUNT(*) FROM image_views"); $view_count = (int)$database->get_one(
else // return view count of specified image "SELECT COUNT(*) FROM image_views"
$view_count = (int)$database->get_one("SELECT COUNT(*) FROM image_views WHERE ". );
"image_id =:image_id", array("image_id" => $imgid)); else // return view count of specified image
$view_count = (int)$database->get_one(
"SELECT COUNT(*) FROM image_views WHERE image_id =:image_id",
array("image_id" => $imgid)
);
// returns the count as int // returns the count as int
return $view_count; return $view_count;
} }
} }

View File

@ -161,16 +161,16 @@
class SearchTermParseEvent extends Event { class SearchTermParseEvent extends Event {
/** @var null|string */ /** @var null|string */
public $term = null; public $term = null;
/** @var null|array */ /** @var string[] */
public $context = null; public $context = array();
/** @var \Querylet[] */ /** @var \Querylet[] */
public $querylets = array(); public $querylets = array();
/** /**
* @param string|null $term * @param string|null $term
* @param array|null $context * @param string[] $context
*/ */
public function __construct($term, $context) { public function __construct($term, array $context) {
$this->term = $term; $this->term = $term;
$this->context = $context; $this->context = $context;
} }
@ -201,16 +201,16 @@ class SearchTermParseException extends SCoreException {
} }
class PostListBuildingEvent extends Event { class PostListBuildingEvent extends Event {
/** @var null|array */ /** @var array */
public $search_terms = null; public $search_terms = array();
/** @var array */ /** @var array */
public $parts = array(); public $parts = array();
/** /**
* @param array|null $search * @param string[] $search
*/ */
public function __construct($search) { public function __construct(array $search) {
$this->search_terms = $search; $this->search_terms = $search;
} }
@ -225,7 +225,8 @@ class PostListBuildingEvent extends Event {
} }
class Index extends Extension { class Index extends Extension {
var $stpen = 0; // search term parse event number /** @var int */
private $stpen = 0; // search term parse event number
public function onInitExt(InitExtEvent $event) { public function onInitExt(InitExtEvent $event) {
global $config; global $config;
@ -238,7 +239,8 @@ class Index extends Extension {
global $database, $page; global $database, $page;
if($event->page_matches("post/list")) { if($event->page_matches("post/list")) {
if(isset($_GET['search'])) { if(isset($_GET['search'])) {
$search = url_escape(Tag::implode(Tag::resolve_aliases(Tag::explode($_GET['search'], false)))); // implode(explode()) to resolve aliases and sanitise
$search = url_escape(Tag::implode(Tag::explode($_GET['search'], false)));
if(empty($search)) { if(empty($search)) {
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/list/1")); $page->set_redirect(make_link("post/list/1"));
@ -263,7 +265,7 @@ class Index extends Extension {
$images = $database->cache->get("post-list:$page_number"); $images = $database->cache->get("post-list:$page_number");
if(!$images) { if(!$images) {
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms); $images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
$database->cache->set("post-list:$page_number", $images, 600); $database->cache->set("post-list:$page_number", $images, 60);
} }
} }
else { else {
@ -310,7 +312,7 @@ class Index extends Extension {
$event->panel->add_block($sb); $event->panel->add_block($sb);
} }
public function onImageInfoSet($event) { public function onImageInfoSet(ImageInfoSetEvent $event) {
global $database; global $database;
if(SPEED_HAX) { if(SPEED_HAX) {
$database->cache->delete("thumb-block:{$event->image->id}"); $database->cache->delete("thumb-block:{$event->image->id}");
@ -322,8 +324,17 @@ class Index extends Extension {
// check for tags first as tag based searches are more common. // check for tags first as tag based searches are more common.
if(preg_match("/^tags([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) { if(preg_match("/^tags([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
$cmp = ltrim($matches[1], ":") ?: "="; $cmp = ltrim($matches[1], ":") ?: "=";
$tags = $matches[2]; $count = $matches[2];
$event->add_querylet(new Querylet('images.id IN (SELECT DISTINCT image_id FROM image_tags GROUP BY image_id HAVING count(image_id) '.$cmp.' '.$tags.')')); $event->add_querylet(
new Querylet("EXISTS (
SELECT 1
FROM image_tags it
LEFT JOIN tags t ON it.tag_id = t.id
WHERE images.id = it.image_id
GROUP BY image_id
HAVING COUNT(*) $cmp $count
)")
);
} }
else if(preg_match("/^ratio([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+):(\d+)$/i", $event->term, $matches)) { else if(preg_match("/^ratio([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+):(\d+)$/i", $event->term, $matches)) {
$cmp = preg_replace('/^:/', '=', $matches[1]); $cmp = preg_replace('/^:/', '=', $matches[1]);
@ -394,4 +405,3 @@ class Index extends Extension {
$this->stpen++; $this->stpen++;
} }
} }

View File

@ -1,7 +1,7 @@
/*jshint bitwise:false, curly:true, eqeqeq:true, evil:true, forin:false, noarg:true, noempty:true, nonew:true, undef:false, strict:false, browser:true, jquery:true */ /*jshint bitwise:false, curly:true, eqeqeq:true, evil:true, forin:false, noarg:true, noempty:true, nonew:true, undef:false, strict:false, browser:true, jquery:true */
$(function() { $(function() {
var blocked_tags = ($.cookie("ui-blocked-tags") || "").split(" "); var blocked_tags = (Cookies.get("ui-blocked-tags") || "").split(" ");
var needs_refresh = false; var needs_refresh = false;
for(var i=0; i<blocked_tags.length; i++) { for(var i=0; i<blocked_tags.length; i++) {
var tag = blocked_tags[i]; var tag = blocked_tags[i];
@ -34,9 +34,9 @@ $(function() {
}); });
function select_blocked_tags() { function select_blocked_tags() {
var blocked_tags = prompt("Enter tags to ignore", $.cookie("ui-blocked-tags") || "My_Little_Pony"); var blocked_tags = prompt("Enter tags to ignore", Cookies.get("ui-blocked-tags") || "My_Little_Pony");
if(blocked_tags !== null) { if(blocked_tags !== null) {
$.cookie("ui-blocked-tags", blocked_tags.toLowerCase(), {path: '/', expires: 365}); Cookies.set("ui-blocked-tags", blocked_tags.toLowerCase(), {expires: 365});
location.reload(true); location.reload(true);
} }
} }

View File

@ -1,5 +1,18 @@
<?php <?php
class IndexTest extends ShimmiePHPUnitTestCase { class IndexTest extends ShimmiePHPUnitTestCase {
private function upload() {
$this->log_in_as_user();
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "thing computer screenshot pbx phone");
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "thing computer computing bedroom workshop");
$this->log_out();
# make sure both uploads were ok
$this->assertTrue($image_id_1 > 0);
$this->assertTrue($image_id_2 > 0);
return array($image_id_1, $image_id_2);
}
public function testIndexPage() { public function testIndexPage() {
$this->get_page('post/list'); $this->get_page('post/list');
$this->assert_title("Welcome to Shimmie ".VERSION); $this->assert_title("Welcome to Shimmie ".VERSION);
@ -24,85 +37,168 @@ class IndexTest extends ShimmiePHPUnitTestCase {
$this->assert_title("Shimmie"); $this->assert_title("Shimmie");
$this->get_page('post/list/99999'); $this->get_page('post/list/99999');
$this->assert_title("No Images Found"); $this->assert_response(404);
# FIXME: test search box
} }
public function testSearches() { /* * * * * * * * * * *
$this->log_in_as_user(); * Tag Search *
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); * * * * * * * * * * */
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "computer bedroom workshop"); public function testTagSearchNoResults() {
$this->log_out(); $image_ids = $this->upload();
# make sure both uploads were ok
$this->assertTrue($image_id_1 > 0);
$this->assertTrue($image_id_2 > 0);
# regular tag, no results
$this->get_page('post/list/maumaumau/1'); $this->get_page('post/list/maumaumau/1');
$this->assert_title("No Images Found"); $this->assert_response(404);
}
public function testTagSearchOneResult() {
$image_ids = $this->upload();
$this->get_page("post/list/pbx/1");
$this->assert_response(302);
}
public function testTagSearchManyResults() {
$image_ids = $this->upload();
# regular tag, many results
$this->get_page('post/list/computer/1'); $this->get_page('post/list/computer/1');
$this->assert_response(200);
$this->assert_title("computer"); $this->assert_title("computer");
$this->assert_no_text("No Images Found"); }
# meta tag, many results /* * * * * * * * * * *
$this->get_page('post/list/size=640x480/1'); * Multi-Tag Search *
$this->assert_title("size=640x480"); * * * * * * * * * * */
$this->assert_no_text("No Images Found"); public function testMultiTagSearchNoResults() {
$image_ids = $this->upload();
# meta tag, one result
$this->get_page("post/list/hash=feb01bab5698a11dd87416724c7a89e3/1");
//$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->assert_no_text("No Images Found");
# meta tag, one result
$this->get_page("post/list/md5=feb01bab5698a11dd87416724c7a89e3/1");
//$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->assert_no_text("No Images Found");
$this->markTestIncomplete();
# multiple tags, many results
$this->get_page('post/list/computer%20size=640x480/1');
$this->assert_title("computer size=640x480");
$this->assert_no_text("No Images Found");
# multiple tags, one of which doesn't exist # multiple tags, one of which doesn't exist
# (test the "one tag doesn't exist = no hits" path) # (test the "one tag doesn't exist = no hits" path)
$this->get_page('post/list/computer%20asdfasdfwaffle/1'); $this->get_page('post/list/computer asdfasdfwaffle/1');
$this->assert_text("No Images Found"); $this->assert_response(404);
}
# multiple tags, single result; search with one result = direct to image public function testMultiTagSearchOneResult() {
$this->get_page('post/list/screenshot%20computer/1'); $image_ids = $this->upload();
//$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->get_page('post/list/computer screenshot/1');
$this->assert_response(302);
}
public function testMultiTagSearchManyResults() {
$image_ids = $this->upload();
$this->get_page('post/list/computer thing/1');
$this->assert_response(200);
}
/* * * * * * * * * * *
* Meta Search *
* * * * * * * * * * */
public function testMetaSearchNoResults() {
$this->get_page('post/list/hash=1234567890/1');
$this->assert_response(404);
}
public function testMetaSearchOneResult() {
$image_ids = $this->upload();
$this->get_page("post/list/hash=feb01bab5698a11dd87416724c7a89e3/1");
$this->assert_response(302);
$this->get_page("post/list/md5=feb01bab5698a11dd87416724c7a89e3/1");
$this->assert_response(302);
$this->get_page("post/list/id={$image_ids[1]}/1");
$this->assert_response(302);
$this->get_page("post/list/filename=screenshot/1");
$this->assert_response(302);
}
public function testMetaSearchManyResults() {
$image_ids = $this->upload();
$this->get_page('post/list/size=640x480/1');
$this->assert_response(200);
$this->get_page("post/list/tags=5/1");
$this->assert_response(200);
$this->get_page("post/list/ext=jpg/1");
$this->assert_response(200);
}
/* * * * * * * * * * *
* Wildcards *
* * * * * * * * * * */
public function testWildSearchNoResults() {
$image_ids = $this->upload();
$this->get_page("post/list/asdfasdf*/1");
$this->assert_response(404);
}
public function testWildSearchOneResult() {
$image_ids = $this->upload();
global $database;
$db = $database->get_driver_name();
if($db == "pgsql" || $db == "sqlite") {
$this->markTestIncomplete();
}
// Only the first image matches both the wildcard and the tag.
// This checks for https://github.com/shish/shimmie2/issues/547
// (comp* is expanded to "computer computing", then we searched
// for images which match two or more of the tags in
// "computer computing screenshot")
$this->get_page("post/list/comp* screenshot/1");
$this->assert_response(302);
}
public function testWildSearchManyResults() {
$image_ids = $this->upload();
// two images match comp* - one matches it once,
// one matches it twice
$this->get_page("post/list/comp*/1");
$this->assert_response(200);
}
/* * * * * * * * * * *
* Mixed *
* * * * * * * * * * */
public function testMixedSearchTagMeta() {
$image_ids = $this->upload();
# multiple tags, many results
$this->get_page('post/list/computer size=640x480/1');
$this->assert_response(200);
}
// tag + negative
// wildcards + ???
/* * * * * * * * * * *
* Other *
* - negative tags *
* - wildcards *
* * * * * * * * * * */
public function testOther() {
$this->markTestIncomplete();
# negative tag, should have one result # negative tag, should have one result
$this->get_page('post/list/computer%20-pbx/1'); $this->get_page('post/list/computer -pbx/1');
//$this->assert_title(new PatternExpectation("/^Image $image_id_2: /")); $this->assert_response(302);
# negative tag alone, should work # negative tag alone, should work
# FIXME: known broken in mysql # FIXME: known broken in mysql
//$this->get_page('post/list/-pbx/1'); //$this->get_page('post/list/-pbx/1');
//$this->assert_title(new PatternExpectation("/^Image $image_id_2: /")); //$this->assert_response(302);
# test various search methods # test various search methods
$this->get_page("post/list/bedroo*/1"); $this->get_page("post/list/bedroo*/1");
//$this->assert_title(new PatternExpectation("/^Image $image_id_2: /")); $this->assert_response(302);
$this->get_page("post/list/id=$image_id_1/1");
//$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->assert_no_text("No Images Found");
$this->get_page("post/list/filename=screenshot/1");
//$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->assert_no_text("No Images Found");
$this->get_page("post/list/tags=3/1");
$this->assert_title("tags=3");
$this->assert_no_text("No Images Found");
$this->get_page("post/list/ext=jpg/1");
$this->assert_title("ext=jpg");
$this->assert_no_text("No Images Found");
} }
} }

View File

@ -1,7 +1,7 @@
<?php <?php
class IndexTheme extends Themelet { class IndexTheme extends Themelet {
var $page_number, $total_pages, $search_terms; protected $page_number, $total_pages, $search_terms;
/** /**
* @param int $page_number * @param int $page_number

View File

@ -14,7 +14,7 @@
// RemoveIPBanEvent {{{ // RemoveIPBanEvent {{{
class RemoveIPBanEvent extends Event { class RemoveIPBanEvent extends Event {
var $id; public $id;
public function __construct($id) { public function __construct($id) {
$this->id = $id; $this->id = $id;
@ -23,9 +23,9 @@ class RemoveIPBanEvent extends Event {
// }}} // }}}
// AddIPBanEvent {{{ // AddIPBanEvent {{{
class AddIPBanEvent extends Event { class AddIPBanEvent extends Event {
var $ip; public $ip;
var $reason; public $reason;
var $end; public $end;
public function __construct(/*string(ip)*/ $ip, /*string*/ $reason, /*string*/ $end) { public function __construct(/*string(ip)*/ $ip, /*string*/ $reason, /*string*/ $end) {
$this->ip = trim($ip); $this->ip = trim($ip);
@ -222,9 +222,9 @@ class IPBan extends Extension {
print "IP <b>$ip</b> has been banned until <b>$date</b> by <b>{$admin->name}</b> because of <b>$reason</b>\n"; print "IP <b>$ip</b> has been banned until <b>$date</b> by <b>{$admin->name}</b> because of <b>$reason</b>\n";
print "<p>If you couldn't possibly be guilty of what you're banned for, the person we banned probably had a dynamic IP address and so do you. See <a href='http://whatismyipaddress.com/dynamic-static'>http://whatismyipaddress.com/dynamic-static</a> for more information.\n"; print "<p>If you couldn't possibly be guilty of what you're banned for, the person we banned probably had a dynamic IP address and so do you. See <a href='http://whatismyipaddress.com/dynamic-static'>http://whatismyipaddress.com/dynamic-static</a> for more information.\n";
$contact_link = $config->get_string("contact_link"); $contact_link = contact_link();
if(!empty($contact_link)) { if(!empty($contact_link)) {
print "<p><a href='$contact_link'>Contact The Admin</a>"; print "<p><a href='$contact_link'>Contact the staff (be sure to include this message)</a>";
} }
exit; exit;
} }

View File

@ -85,8 +85,7 @@ class LinkImageTheme extends Themelet {
return " return "
<tr> <tr>
<td><label for='".$id."' title='Click to select the textbox'>$label</label></td> <td><label for='".$id."' title='Click to select the textbox'>$label</label></td>
<td><input type='text' readonly='readonly' id='".$id."' name='".$id."' <td><input type='text' readonly='readonly' id='".$id."' name='".$id."' value='".html_escape($content)."' onfocus='this.select();' /></td>
value='".html_escape($content)."' onfocus='this.select();'></input></td>
</tr> </tr>
"; ";
} }

View File

@ -15,11 +15,11 @@ class LiveFeed extends Extension {
$event->panel->add_block($sb); $event->panel->add_block($sb);
} }
public function onUserCreation($event) { public function onUserCreation(UserCreationEvent $event) {
$this->msg("New user created: {$event->username}"); $this->msg("New user created: {$event->username}");
} }
public function onImageAddition($event) { public function onImageAddition(ImageAdditionEvent $event) {
global $user; global $user;
$this->msg( $this->msg(
make_http(make_link("post/view/".$event->image->id))." - ". make_http(make_link("post/view/".$event->image->id))." - ".
@ -27,14 +27,14 @@ class LiveFeed extends Extension {
); );
} }
public function onTagSet($event) { public function onTagSet(TagSetEvent $event) {
$this->msg( $this->msg(
make_http(make_link("post/view/".$event->image->id))." - ". make_http(make_link("post/view/".$event->image->id))." - ".
"tags set to: ".Tag::implode($event->tags) "tags set to: ".Tag::implode($event->tags)
); );
} }
public function onCommentPosting($event) { public function onCommentPosting(CommentPostingEvent $event) {
global $user; global $user;
$this->msg( $this->msg(
make_http(make_link("post/view/".$event->image_id))." - ". make_http(make_link("post/view/".$event->image_id))." - ".
@ -42,14 +42,19 @@ class LiveFeed extends Extension {
); );
} }
public function onImageInfoSet($event) { public function onImageInfoSet(ImageInfoSetEvent $event) {
# $this->msg("Image info set"); # $this->msg("Image info set");
} }
public function get_priority() {return 99;} public function get_priority() {return 99;}
/**
* @param string $data
*/
private function msg($data) { private function msg($data) {
global $config; global $config;
assert('is_string($data)');
$host = $config->get_string("livefeed_host", "127.0.0.1:25252"); $host = $config->get_string("livefeed_host", "127.0.0.1:25252");
if(!$host) { return; } if(!$host) { return; }

View File

@ -8,7 +8,7 @@
*/ */
class LogNet extends Extension { class LogNet extends Extension {
var $count = 0; private $count = 0;
public function onLog(LogEvent $event) { public function onLog(LogEvent $event) {
global $user; global $user;

View File

@ -26,12 +26,11 @@ class MassTagger extends Extension {
if($event->page_matches("mass_tagger/tag") && $user->is_admin()) { if($event->page_matches("mass_tagger/tag") && $user->is_admin()) {
if( !isset($_POST['ids']) or !isset($_POST['tag']) ) return; if( !isset($_POST['ids']) or !isset($_POST['tag']) ) return;
$tag = $_POST['tag']; $tags = Tag::explode($_POST['tag']);
$tag_array = explode(" ",$tag);
$pos_tag_array = array(); $pos_tag_array = array();
$neg_tag_array = array(); $neg_tag_array = array();
foreach($tag_array as $new_tag) { foreach($tags as $new_tag) {
if (strpos($new_tag, '-') === 0) if (strpos($new_tag, '-') === 0)
$neg_tag_array[] = substr($new_tag,1); $neg_tag_array[] = substr($new_tag,1);
else else
@ -45,18 +44,19 @@ class MassTagger extends Extension {
if(isset($_POST['setadd']) && $_POST['setadd'] == 'set') { if(isset($_POST['setadd']) && $_POST['setadd'] == 'set') {
foreach($images as $image) { foreach($images as $image) {
$image->set_tags(Tag::explode($tag)); $image->set_tags($tags);
} }
} }
else { else {
foreach($images as $image) { foreach($images as $image) {
if (!empty($neg_tag_array)) { if (!empty($neg_tag_array)) {
$img_tags = array_merge($pos_tag_array, explode(" ",$image->get_tag_list())); $img_tags = array_merge($pos_tag_array, $image->get_tag_array());
$img_tags = array_diff($img_tags, $neg_tag_array); $img_tags = array_diff($img_tags, $neg_tag_array);
$image->set_tags(Tag::explode($img_tags)); $image->set_tags($img_tags);
}
else {
$image->set_tags(array_merge($tags, $image->get_tag_array()));
} }
else
$image->set_tags(Tag::explode($tag . " " . $image->get_tag_list()));
} }
} }

View File

@ -24,13 +24,18 @@ function toggle_tag( button, id ) {
var string = list.val(); var string = list.val();
if( (string.indexOf(id) == 0) || (string.indexOf(":"+id) > -1) ) { if( (string.indexOf(id) == 0) || (string.indexOf(":"+id) > -1) ) {
$(button).css('border', 'none'); $(button).removeClass('mass-tagger-selected');
string = string.replace(id, ''); string = string.replace(id, '');
list.val(string); list.val(string);
} }
else { else {
$(button).css('border', '3px solid blue'); $(button).addClass('mass-tagger-selected');
string += id; string += id;
list.val(string); list.val(string);
} }
} }
$(function () {
// Clear the selection, in case it was autocompleted by the browser.
$('#mass_tagger_ids').val("");
});

View File

@ -0,0 +1,3 @@
.mass-tagger-selected {
border: 3px solid blue;
}

View File

Before

Width:  |  Height:  |  Size: 43 B

After

Width:  |  Height:  |  Size: 43 B

View File

@ -72,37 +72,25 @@ class Notes extends Extension {
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event) {
global $page, $user; global $page, $user;
if($event->page_matches("note")) { if($event->page_matches("note")) {
switch($event->get_arg(0)) { switch($event->get_arg(0)) {
case "list": //index case "list": //index
{
$this->get_notes_list($event); // This should show images like post/list but i don't know how do that. $this->get_notes_list($event); // This should show images like post/list but i don't know how do that.
break; break;
}
case "requests": // The same as post/list but only for note_request table. case "requests": // The same as post/list but only for note_request table.
{ $this->get_notes_requests($event); // This should show images like post/list but i don't know how do that.
$this->get_notes_requests($event); // This should shouw images like post/list but i don't know how do that.
break; break;
}
case "search": case "search":
{
if(!$user->is_anonymous()) if(!$user->is_anonymous())
$this->theme->search_notes_page($page); $this->theme->search_notes_page($page);
break; break;
} case "updated": //Thinking how to build this function.
case "updated": //Thinking how biuld this function.
{
$this->get_histories($event); $this->get_histories($event);
break; break;
} case "history": //Thinking how to build this function.
case "history": //Thinking how biuld this function.
{
$this->get_history($event); $this->get_history($event);
break; break;
}
case "revert": case "revert":
{ $noteID = $event->get_arg(1);
$noteID = $event->get_arg(1);
$reviewID = $event->get_arg(2); $reviewID = $event->get_arg(2);
if(!$user->is_anonymous()){ if(!$user->is_anonymous()){
$this->revert_history($noteID, $reviewID); $this->revert_history($noteID, $reviewID);
@ -111,67 +99,52 @@ class Notes extends Extension {
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("note/updated")); $page->set_redirect(make_link("note/updated"));
break; break;
}
case "add_note": case "add_note":
{
if(!$user->is_anonymous()) if(!$user->is_anonymous())
$this->add_new_note(); $this->add_new_note();
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"])); $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break; break;
}
case "add_request": case "add_request":
{
if(!$user->is_anonymous()) if(!$user->is_anonymous())
$this->add_note_request(); $this->add_note_request();
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"])); $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break; break;
}
case "nuke_notes": case "nuke_notes":
{
if($user->is_admin()) if($user->is_admin())
$this->nuke_notes(); $this->nuke_notes();
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"])); $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break; break;
}
case "nuke_requests": case "nuke_requests":
{
if($user->is_admin()) if($user->is_admin())
$this->nuke_requests(); $this->nuke_requests();
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"])); $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break; break;
}
case "edit_note": case "edit_note":
{
if (!$user->is_anonymous()) { if (!$user->is_anonymous()) {
$this->update_note(); $this->update_note();
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"])); $page->set_redirect(make_link("post/view/" . $_POST["image_id"]));
} }
break; break;
}
case "delete_note": case "delete_note":
{
if ($user->is_admin()) { if ($user->is_admin()) {
$this->delete_note(); $this->delete_note();
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"])); $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
} }
break; break;
}
default: default:
{
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("note/list")); $page->set_redirect(make_link("note/list"));
break; break;
}
} }
} }
} }
@ -184,7 +157,7 @@ class Notes extends Extension {
global $page, $user; global $page, $user;
//display form on image event //display form on image event
$notes = $this->get_notes($event->image->id); $notes = $this->get_notes($event->image->id);
$this->theme->display_note_system($page, $event->image->id, $notes, $user->is_admin()); $this->theme->display_note_system($page, $event->image->id, $notes, $user->is_admin());
} }
@ -223,8 +196,7 @@ class Notes extends Extension {
$user = User::by_name($matches[1]); $user = User::by_name($matches[1]);
if(!is_null($user)) { if(!is_null($user)) {
$user_id = $user->id; $user_id = $user->id;
} } else {
else {
$user_id = -1; $user_id = -1;
} }
@ -250,8 +222,8 @@ class Notes extends Extension {
"SELECT * ". "SELECT * ".
"FROM notes ". "FROM notes ".
"WHERE enable = ? AND image_id = ? ". "WHERE enable = ? AND image_id = ? ".
"ORDER BY date ASC" "ORDER BY date ASC",
, array('1', $imageID)); array('1', $imageID));
} }
@ -270,17 +242,15 @@ class Notes extends Extension {
$noteText = html_escape($_POST["note_text"]); $noteText = html_escape($_POST["note_text"]);
$database->execute(" $database->execute("
INSERT INTO notes INSERT INTO notes (enable, image_id, user_id, user_ip, date, x1, y1, height, width, note)
(enable, image_id, user_id, user_ip, date, x1, y1, height, width, note) VALUES (?, ?, ?, ?, now(), ?, ?, ?, ?, ?)",
VALUES
(?, ?, ?, ?, now(), ?, ?, ?, ?, ?)",
array(1, $imageID, $user_id, $_SERVER['REMOTE_ADDR'], $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText)); array(1, $imageID, $user_id, $_SERVER['REMOTE_ADDR'], $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText));
$noteID = $database->get_last_insert_id('notes_id_seq'); $noteID = $database->get_last_insert_id('notes_id_seq');
log_info("notes", "Note added {$noteID} by {$user->name}"); log_info("notes", "Note added {$noteID} by {$user->name}");
$database->Execute("UPDATE images SET notes=(SELECT COUNT(*) FROM notes WHERE image_id=?) WHERE id=?", array($imageID, $imageID)); $database->execute("UPDATE images SET notes=(SELECT COUNT(*) FROM notes WHERE image_id=?) WHERE id=?", array($imageID, $imageID));
$this->add_history(1, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText); $this->add_history(1, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText);
} }
@ -297,15 +267,13 @@ class Notes extends Extension {
$user_id = $user->id; $user_id = $user->id;
$database->execute(" $database->execute("
INSERT INTO note_request INSERT INTO note_request (image_id, user_id, date)
(image_id, user_id, date) VALUES (?, ?, now())",
VALUES
(?, ?, now())",
array($image_id, $user_id)); array($image_id, $user_id));
$resultID = $database->get_last_insert_id('note_request_id_seq'); $resultID = $database->get_last_insert_id('note_request_id_seq');
log_info("notes", "Note requested {$requestID} by {$user->name}"); log_info("notes", "Note requested {$resultID} by {$user->name}");
} }
@ -313,63 +281,53 @@ class Notes extends Extension {
/* /*
* HERE WE EDIT THE NOTE * HERE WE EDIT THE NOTE
*/ */
private function update_note() private function update_note() {
{ global $database;
$imageID = int_escape($_POST["image_id"]);
$noteID = int_escape($_POST["note_id"]); $note = array(
$noteX1 = int_escape($_POST["note_x1"]); "noteX1" => int_escape($_POST["note_x1"]),
$noteY1 = int_escape($_POST["note_y1"]); "noteY1" => int_escape($_POST["note_y1"]),
$noteHeight = int_escape($_POST["note_height"]); "noteHeight" => int_escape($_POST["note_height"]),
$noteWidth = int_escape($_POST["note_width"]); "noteWidth" => int_escape($_POST["note_width"]),
$noteText = sql_escape(html_escape($_POST["note_text"])); "noteText" => sql_escape(html_escape($_POST["note_text"])),
"imageID" => int_escape($_POST["image_id"]),
"noteID" => int_escape($_POST["note_id"])
);
// validate parameters // validate parameters
if (is_null($imageID) || !is_numeric($imageID) || if (array_search(NULL, $note)|| strlen($note['noteText']) == 0) {
is_null($noteID) || !is_numeric($noteID) ||
is_null($noteX1) || !is_numeric($noteX1) ||
is_null($noteY1) || !is_numeric($noteY1) ||
is_null($noteHeight) || !is_numeric($noteHeight) ||
is_null($noteWidth) || !is_numeric($noteWidth) ||
is_null($noteText) || strlen($noteText) == 0)
{
return; return;
} }
global $database;
$database->execute("UPDATE notes ". $database->execute("UPDATE notes ".
"SET x1 = ?, ". "SET x1 = ?, ".
"y1 = ?, ". "y1 = ?, ".
"height = ?, ". "height = ?, ".
"width = ?,". "width = ?,".
"note = ? ". "note = ? ".
"WHERE image_id = ? AND id = ?", array($noteX1, $noteY1, $noteHeight, $noteWidth, $noteText, $imageID, $noteID)); "WHERE image_id = ? AND id = ?", array_values($note));
$this->add_history(1, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText); $this->add_history(1, $note['noteID'], $note['imageID'], $note['noteX1'], $note['noteY1'], $note['noteHeight'], $note['noteWidth'], $note['noteText']);
} }
/* /*
* HERE WE DELETE THE NOTE * HERE WE DELETE THE NOTE
*/ */
private function delete_note() private function delete_note() {
{ global $user, $database;
global $user;
$imageID = int_escape($_POST["image_id"]); $imageID = int_escape($_POST["image_id"]);
$noteID = int_escape($_POST["note_id"]); $noteID = int_escape($_POST["note_id"]);
// validate parameters // validate parameters
if( is_null($imageID) || !is_numeric($imageID) || if(is_null($imageID) || !is_numeric($imageID) || is_null($noteID) || !is_numeric($noteID)) {
is_null($noteID) || !is_numeric($noteID))
{
return; return;
} }
global $database;
$database->execute("UPDATE notes ". $database->execute("UPDATE notes ".
"SET enable = ? ". "SET enable = ? ".
"WHERE image_id = ? AND id = ?", array(0, $imageID, $noteID)); "WHERE image_id = ? AND id = ?", array(0, $imageID, $noteID));
log_info("notes", "Note deleted {$noteID} by {$user->name}"); log_info("notes", "Note deleted {$noteID} by {$user->name}");
} }
@ -408,33 +366,26 @@ class Notes extends Extension {
global $database, $config; global $database, $config;
$pageNumber = $event->get_arg(1); $pageNumber = $event->get_arg(1);
if(is_null($pageNumber) || !is_numeric($pageNumber) || $pageNumber <= 0) {
if(is_null($pageNumber) || !is_numeric($pageNumber)) $pageNumber = 0;
$pageNumber = 0; } else {
else if ($pageNumber <= 0) $pageNumber--;
$pageNumber = 0; }
else
$pageNumber--;
$notesPerPage = $config->get_int('notesNotesPerPage'); $notesPerPage = $config->get_int('notesNotesPerPage');
//$result = $database->get_all("SELECT * FROM pool_images WHERE pool_id=?", array($poolID)); //$result = $database->get_all("SELECT * FROM pool_images WHERE pool_id=?", array($poolID));
$result = $database->execute("SELECT DISTINCT image_id".
$get_notes = " "FROM notes ".
SELECT DISTINCT image_id ". "WHERE enable = ? ".
"FROM notes ". "ORDER BY date DESC LIMIT ?, ?",
"WHERE enable = ? ". array(1, $pageNumber * $notesPerPage, $notesPerPage));
"ORDER BY date DESC LIMIT ?, ?";
$result = $database->Execute($get_notes, array(1, $pageNumber * $notesPerPage, $notesPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(DISTINCT image_id) FROM notes") / $notesPerPage); $totalPages = ceil($database->get_one("SELECT COUNT(DISTINCT image_id) FROM notes") / $notesPerPage);
$images = array(); $images = array();
while(!$result->EOF) { while($row = $result->fetch()) {
$image = Image::by_id($result->fields["image_id"]); $images[] = array(Image::by_id($row["image_id"]));
$images[] = array($image);
$result->MoveNext();
} }
$this->theme->display_note_list($images, $pageNumber + 1, $totalPages); $this->theme->display_note_list($images, $pageNumber + 1, $totalPages);
@ -445,36 +396,32 @@ class Notes extends Extension {
* @param PageRequestEvent $event * @param PageRequestEvent $event
*/ */
private function get_notes_requests(PageRequestEvent $event) { private function get_notes_requests(PageRequestEvent $event) {
global $config; global $config, $database;
$pageNumber = $event->get_arg(1); $pageNumber = $event->get_arg(1);
if(is_null($pageNumber) || !is_numeric($pageNumber) || $pageNumber <= 0) {
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0; $pageNumber = 0;
else if ($pageNumber <= 0) } else {
$pageNumber = 0;
else
$pageNumber--; $pageNumber--;
}
$requestsPerPage = $config->get_int('notesRequestsPerPage'); $requestsPerPage = $config->get_int('notesRequestsPerPage');
//$result = $database->get_all("SELECT * FROM pool_images WHERE pool_id=?", array($poolID)); //$result = $database->get_all("SELECT * FROM pool_images WHERE pool_id=?", array($poolID));
global $database;
$get_requests = "
SELECT DISTINCT image_id ".
"FROM note_request ".
"ORDER BY date DESC LIMIT ?, ?";
$result = $database->Execute($get_requests, array($pageNumber * $requestsPerPage, $requestsPerPage));
$result = $database->execute("
SELECT DISTINCT image_id
FROM note_request
ORDER BY date DESC LIMIT ?, ?",
array($pageNumber * $requestsPerPage, $requestsPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_request") / $requestsPerPage); $totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_request") / $requestsPerPage);
$images = array(); $images = array();
while(!$result->EOF) { while($row = $result->fetch()) {
$image = Image::by_id($result->fields["image_id"]); $images[] = array(Image::by_id($row["image_id"]));
$images[] = array($image);
$result->MoveNext();
} }
$this->theme->display_note_requests($images, $pageNumber + 1, $totalPages); $this->theme->display_note_requests($images, $pageNumber + 1, $totalPages);
@ -488,17 +435,13 @@ class Notes extends Extension {
private function add_history($noteEnable, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText){ private function add_history($noteEnable, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText){
global $user, $database; global $user, $database;
$userID = $user->id;
$reviewID = $database->get_one("SELECT COUNT(*) FROM note_histories WHERE note_id = ?", array($noteID)); $reviewID = $database->get_one("SELECT COUNT(*) FROM note_histories WHERE note_id = ?", array($noteID));
$reviewID = $reviewID + 1; $reviewID = $reviewID + 1;
$database->execute(" $database->execute("
INSERT INTO note_histories INSERT INTO note_histories (note_enable, note_id, review_id, image_id, user_id, user_ip, date, x1, y1, height, width, note)
(note_enable, note_id, review_id, image_id, user_id, user_ip, date, x1, y1, height, width, note) VALUES (?, ?, ?, ?, ?, ?, now(), ?, ?, ?, ?, ?)",
VALUES array($noteEnable, $noteID, $reviewID, $imageID, $user->id, $_SERVER['REMOTE_ADDR'], $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText));
(?, ?, ?, ?, ?, ?, now(), ?, ?, ?, ?, ?)",
array($noteEnable, $noteID, $reviewID, $imageID, $userID, $_SERVER['REMOTE_ADDR'], $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText));
} }
@ -507,28 +450,26 @@ class Notes extends Extension {
* @param PageRequestEvent $event * @param PageRequestEvent $event
*/ */
private function get_histories(PageRequestEvent $event){ private function get_histories(PageRequestEvent $event){
global $config, $database;
$pageNumber = $event->get_arg(1); $pageNumber = $event->get_arg(1);
if(is_null($pageNumber) || !is_numeric($pageNumber)) if (is_null($pageNumber) || !is_numeric($pageNumber) || $pageNumber <= 0) {
$pageNumber = 0; $pageNumber = 0;
else if ($pageNumber <= 0) } else {
$pageNumber = 0; $pageNumber--;
else }
$pageNumber--;
global $config; $historiesPerPage = $config->get_int('notesHistoriesPerPage');
$histiriesPerPage = $config->get_int('notesHistoriesPerPage');
//ORDER BY IMAGE & DATE //ORDER BY IMAGE & DATE
global $database;
$histories = $database->get_all("SELECT h.note_id, h.review_id, h.image_id, h.date, h.note, u.name AS user_name ". $histories = $database->get_all("SELECT h.note_id, h.review_id, h.image_id, h.date, h.note, u.name AS user_name ".
"FROM note_histories AS h ". "FROM note_histories AS h ".
"INNER JOIN users AS u ". "INNER JOIN users AS u ".
"ON u.id = h.user_id ". "ON u.id = h.user_id ".
"ORDER BY date DESC LIMIT ?, ?", "ORDER BY date DESC LIMIT ?, ?",
array($pageNumber * $histiriesPerPage, $histiriesPerPage)); array($pageNumber * $historiesPerPage, $historiesPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_histories") / $histiriesPerPage); $totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_histories") / $historiesPerPage);
$this->theme->display_histories($histories, $pageNumber + 1, $totalPages); $this->theme->display_histories($histories, $pageNumber + 1, $totalPages);
} }
@ -539,29 +480,28 @@ class Notes extends Extension {
* @param PageRequestEvent $event * @param PageRequestEvent $event
*/ */
private function get_history(PageRequestEvent $event){ private function get_history(PageRequestEvent $event){
global $config, $database;
$noteID = $event->get_arg(1); $noteID = $event->get_arg(1);
$pageNumber = $event->get_arg(2); $pageNumber = $event->get_arg(2);
if(is_null($pageNumber) || !is_numeric($pageNumber)) if (is_null($pageNumber) || !is_numeric($pageNumber) || $pageNumber <= 0) {
$pageNumber = 0; $pageNumber = 0;
else if ($pageNumber <= 0) } else {
$pageNumber = 0; $pageNumber--;
else }
$pageNumber--;
global $config; $historiesPerPage = $config->get_int('notesHistoriesPerPage');
$histiriesPerPage = $config->get_int('notesHistoriesPerPage');
global $database;
$histories = $database->get_all("SELECT h.note_id, h.review_id, h.image_id, h.date, h.note, u.name AS user_name ". $histories = $database->get_all("SELECT h.note_id, h.review_id, h.image_id, h.date, h.note, u.name AS user_name ".
"FROM note_histories AS h ". "FROM note_histories AS h ".
"INNER JOIN users AS u ". "INNER JOIN users AS u ".
"ON u.id = h.user_id ". "ON u.id = h.user_id ".
"WHERE note_id = ? ". "WHERE note_id = ? ".
"ORDER BY date DESC LIMIT ?, ?", "ORDER BY date DESC LIMIT ?, ?",
array($noteID, $pageNumber * $histiriesPerPage, $histiriesPerPage)); array($noteID, $pageNumber * $historiesPerPage, $historiesPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_histories WHERE note_id = ?", array($noteID)) / $histiriesPerPage); $totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_histories WHERE note_id = ?", array($noteID)) / $historiesPerPage);
$this->theme->display_history($histories, $pageNumber + 1, $totalPages); $this->theme->display_history($histories, $pageNumber + 1, $totalPages);
} }
@ -574,27 +514,22 @@ class Notes extends Extension {
private function revert_history($noteID, $reviewID){ private function revert_history($noteID, $reviewID){
global $database; global $database;
$history = $database->get_row("SELECT * FROM note_histories WHERE note_id = ? AND review_id = ?",array($noteID, $reviewID)); $history = $database->get_row("SELECT * FROM note_histories WHERE note_id = ? AND review_id = ?", array($noteID, $reviewID));
$noteEnable = $history['note_enable']; $noteEnable = $history['note_enable'];
$noteID = $history['note_id']; $noteID = $history['note_id'];
$imageID = $history['image_id']; $imageID = $history['image_id'];
$noteX1 = $history['x1']; $noteX1 = $history['x1'];
$noteY1 = $history['y1']; $noteY1 = $history['y1'];
$noteHeight = $history['height']; $noteHeight = $history['height'];
$noteWidth = $history['width']; $noteWidth = $history['width'];
$noteText = $history['note']; $noteText = $history['note'];
$database->execute("UPDATE notes ". $database->execute("UPDATE notes ".
"SET enable = ?, ". "SET enable = ?, x1 = ?, y1 = ?, height = ?, width = ?, note = ? ".
"x1 = ?, ". "WHERE image_id = ? AND id = ?",
"y1 = ?, ". array(1, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText, $imageID, $noteID));
"height = ?, ".
"width = ?,".
"note = ? ".
"WHERE image_id = ? AND id = ?", array(1, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText, $imageID, $noteID));
$this->add_history($noteEnable, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText); $this->add_history($noteEnable, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText);
} }
} }

View File

@ -4,6 +4,9 @@ $(function() {
if(window.notes) { if(window.notes) {
$('#main_image').load(function(){ $('#main_image').load(function(){
$('#main_image').imgNotes({notes: window.notes}); $('#main_image').imgNotes({notes: window.notes});
//Make sure notes are always shown
$('#main_image').off('mouseenter mouseleave');
}); });
} }

View File

@ -1,7 +1,8 @@
.note { .note {
display: none; display: block;
background-color: #fffdef;
border: #412a21 1px solid; background-color: #FFE;
border: 1px dashed black;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
z-index: 0; z-index: 0;
@ -75,3 +76,12 @@
.imgareaselect-selection { .imgareaselect-selection {
} }
/* Makes sure the note block is hidden */
section#note_system {
height: 0;
}
section#note_system > .blockbody {
padding: 0;
border: 0;
}

View File

@ -43,29 +43,32 @@ class NotesTheme extends Themelet {
} }
// check action POST on form // check action POST on form
public function display_note_system(Page $page, $image_id, $recovered_notes, $adminOptions) public function display_note_system(Page $page, $image_id, $recovered_notes, $adminOptions) {
{ $base_href = get_base_href();
$page->add_html_header("<script src='$base_href/ext/notes/lib/jquery.imgnotes-1.0.min.js' type='text/javascript'></script>");
$page->add_html_header("<script src='$base_href/ext/notes/lib/jquery.imgareaselect-1.0.0-rc1.min.js' type='text/javascript'></script>");
$page->add_html_header("<link rel='stylesheet' type='text/css' href='$base_href/ext/notes/lib/jquery.imgnotes-1.0.min.css' />");
$to_json = array(); $to_json = array();
foreach($recovered_notes as $note) foreach($recovered_notes as $note) {
{
$parsedNote = $note["note"]; $parsedNote = $note["note"];
$parsedNote = str_replace("\n", "\\n", $parsedNote); $parsedNote = str_replace("\n", "\\n", $parsedNote);
$parsedNote = str_replace("\r", "\\r", $parsedNote); $parsedNote = str_replace("\r", "\\r", $parsedNote);
$to_json[] = array( $to_json[] = array(
'x1' => $note["x1"], 'x1' => $note["x1"],
'y1' => $note["y1"], 'y1' => $note["y1"],
'height' => $note["height"], 'height' => $note["height"],
'width' => $note["width"], 'width' => $note["width"],
'note' => $parsedNote, 'note' => $parsedNote,
'note_id' => $note["id"], 'note_id' => $note["id"],
); );
} }
$html = "<script type='text/javascript'>"; $html = "<script type='text/javascript'>notes = ".json_encode($to_json)."</script>";
$html .= "notes = " . json_encode($to_json);
$html .= "</script>
$html .= "
<div id='noteform'> <div id='noteform'>
".make_form(make_link("note/add_note"))." ".make_form(make_link("note/add_note"))."
<input type='hidden' name='image_id' value='".$image_id."' /> <input type='hidden' name='image_id' value='".$image_id."' />
@ -124,7 +127,7 @@ class NotesTheme extends Themelet {
$html .= "</div>"; $html .= "</div>";
$page->add_block(new Block(null, $html, "main", 1)); $page->add_block(new Block(null, $html, "main", 1, 'note_system'));
} }
@ -137,8 +140,8 @@ class NotesTheme extends Themelet {
$thumb_html = $this->build_thumb_html($image); $thumb_html = $this->build_thumb_html($image);
$pool_images .= '<span class="thumb">'. $pool_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'. ' <a href="$image_link">'.$thumb_html.'</a>'.
'</span>'; '</span>';
} }
@ -151,6 +154,7 @@ class NotesTheme extends Themelet {
public function display_note_requests($images, $pageNumber, $totalPages) { public function display_note_requests($images, $pageNumber, $totalPages) {
global $page; global $page;
$pool_images = ''; $pool_images = '';
foreach($images as $pair) { foreach($images as $pair) {
$image = $pair[0]; $image = $pair[0];
@ -158,8 +162,8 @@ class NotesTheme extends Themelet {
$thumb_html = $this->build_thumb_html($image); $thumb_html = $this->build_thumb_html($image);
$pool_images .= '<span class="thumb">'. $pool_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'. ' <a href="$image_link">'.$thumb_html.'</a>'.
'</span>'; '</span>';
} }
@ -174,19 +178,19 @@ class NotesTheme extends Themelet {
global $user; global $user;
$html = "<table id='poolsList' class='zebra'>". $html = "<table id='poolsList' class='zebra'>".
"<thead><tr>". "<thead><tr>".
"<th>Image</th>". "<th>Image</th>".
"<th>Note</th>". "<th>Note</th>".
"<th>Body</th>". "<th>Body</th>".
"<th>Updater</th>". "<th>Updater</th>".
"<th>Date</th>"; "<th>Date</th>";
if(!$user->is_anonymous()){ if(!$user->is_anonymous()){
$html .= "<th>Action</th>"; $html .= "<th>Action</th>";
} }
$html .= "</tr></thead>". $html .= "</tr></thead>".
"<tbody>"; "<tbody>";
foreach($histories as $history) { foreach($histories as $history) {
$image_link = "<a href='".make_link("post/view/".$history['image_id'])."'>".$history['image_id']."</a>"; $image_link = "<a href='".make_link("post/view/".$history['image_id'])."'>".$history['image_id']."</a>";
@ -195,11 +199,11 @@ class NotesTheme extends Themelet {
$revert_link = "<a href='".make_link("note/revert/".$history['note_id']."/".$history['review_id'])."'>Revert</a>"; $revert_link = "<a href='".make_link("note/revert/".$history['note_id']."/".$history['review_id'])."'>Revert</a>";
$html .= "<tr>". $html .= "<tr>".
"<td>".$image_link."</td>". "<td>".$image_link."</td>".
"<td>".$history_link."</td>". "<td>".$history_link."</td>".
"<td style='text-align:left;'>".$history['note']."</td>". "<td style='text-align:left;'>".$history['note']."</td>".
"<td>".$user_link."</td>". "<td>".$user_link."</td>".
"<td>".autodate($history['date'])."</td>"; "<td>".autodate($history['date'])."</td>";
if(!$user->is_anonymous()){ if(!$user->is_anonymous()){
$html .= "<td>".$revert_link."</td>"; $html .= "<td>".$revert_link."</td>";
@ -236,4 +240,3 @@ class NotesTheme extends Themelet {
$this->display_paginator($page, "note/updated", null, $pageNumber, $totalPages); $this->display_paginator($page, "note/updated", null, $pageNumber, $totalPages);
} }
} }

View File

@ -11,7 +11,7 @@
*/ */
class NumericScoreSetEvent extends Event { class NumericScoreSetEvent extends Event {
var $image_id, $user, $score; public $image_id, $user, $score;
/** /**
* @param int $image_id * @param int $image_id
@ -247,9 +247,9 @@ class NumericScore extends Extension {
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)", "images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)",
array("ns_user_id"=>$iid))); array("ns_user_id"=>$iid)));
} }
else if(preg_match("/^order[=|:](numeric_)?(score)[_]?(desc|asc)?$/i", $event->term, $matches)){ else if(preg_match("/^order[=|:](?:numeric_)?(score)(?:_(desc|asc))?$/i", $event->term, $matches)){
$default_order_for_column = "DESC"; $default_order_for_column = "DESC";
$sort = isset($matches[3]) ? strtoupper($matches[3]) : $default_order_for_column; $sort = isset($matches[2]) ? strtoupper($matches[2]) : $default_order_for_column;
Image::$order_sql = "images.numeric_score $sort"; Image::$order_sql = "images.numeric_score $sort";
$event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag $event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag
} }
@ -258,7 +258,7 @@ class NumericScore extends Extension {
public function onTagTermParse(TagTermParseEvent $event) { public function onTagTermParse(TagTermParseEvent $event) {
$matches = array(); $matches = array();
if(preg_match("/^vote[=|:](up|down|remove)$/", $event->term, $matches)) { if(preg_match("/^vote[=|:](up|down|remove)$/", $event->term, $matches) && $event->parse) {
global $user; global $user;
$score = ($matches[1] == "up" ? 1 : ($matches[1] == "down" ? -1 : 0)); $score = ($matches[1] == "up" ? 1 : ($matches[1] == "down" ? -1 : 0));
if(!$user->is_anonymous()) { if(!$user->is_anonymous()) {

View File

@ -31,7 +31,7 @@ class Oekaki extends Extension {
$metadata = array(); $metadata = array();
$metadata['filename'] = 'oekaki.png'; $metadata['filename'] = 'oekaki.png';
$metadata['extension'] = $pathinfo['extension']; $metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = 'oekaki tagme'; $metadata['tags'] = Tag::explode('oekaki tagme');
$metadata['source'] = null; $metadata['source'] = null;
$duev = new DataUploadEvent($tmpname, $metadata); $duev = new DataUploadEvent($tmpname, $metadata);
send_event($duev); send_event($duev);

Some files were not shown because too many files have changed in this diff Show More