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

View File

@ -3,11 +3,16 @@
</IfModule>
<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>
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteEngine On
# rather than link to images/ha/hash and have an ugly filename,
# we link to images/hash/tags.ext; mod_rewrite splits things so
@ -20,14 +25,6 @@
RewriteRule ^(.*)$ index.php?q=$1&%{QUERY_STRING} [L]
</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>
ExpiresActive On
<FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|css|js))$">
@ -40,7 +37,31 @@ DefaultType image/jpeg
#ExpiresByType text/plain "now"
</IfModule>
<ifmodule mod_deflate.c>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
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
filter:
excluded_paths: [lib/*,ext/tagger/script.js,ext/chatbox/*]
excluded_paths: [lib/*,ext/*/lib/*,ext/tagger/script.js,ext/chatbox/*]
tools:
external_code_coverage: true

View File

@ -1,43 +1,62 @@
language: php
sudo: false
php:
- 5.4
- 5.5
- 5.6
- nightly
- 5.6
- 7.0
- 7.1
sudo: false
env:
matrix:
- DB=mysql
- DB=pgsql
- DB=sqlite
- DB=mysql
- DB=pgsql
- 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:
- 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 echo '<?php define("DATABASE_DSN", "pgsql:user=postgres;password=;host=;dbname=shimmie");' > data/config/auto_install.conf.php ; fi
- if [[ "$DB" == "mysql" ]]; then mysql -e "SET GLOBAL general_log = 'ON';" -uroot; fi
- if [[ "$DB" == "mysql" ]]; then mysql -e "CREATE DATABASE shimmie;" -uroot; fi
- if [[ "$DB" == "mysql" ]]; then 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
- wget https://scrutinizer-ci.com/ocular.phar
- mkdir -p data/config
- |
if [[ "$DB" == "pgsql" ]]; then
psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres ;
psql -c "CREATE DATABASE shimmie;" -U postgres ;
echo '<?php define("DATABASE_DSN", "pgsql:user=postgres;password=;host=;dbname=shimmie");' > data/config/auto_install.conf.php ;
fi
- |
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:
- php install.php
- phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
- vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
after_failure:
- head -n 100 data/config/*
- ls /var/run/mysql*
- ls /var/log/*mysql*
- cat /var/log/mysql.err
- cat /var/log/mysql.log
- cat /var/log/mysql/error.log
- cat /var/log/mysql/slow.log
- ls /var/log/postgresql
- cat /var/log/postgresql/postgresql*
- head -n 100 data/config/*
- ls /var/run/mysql*
# All of the below commands require sudo, which we can't use without losing some speed & caching.
# SEE: https://docs.travis-ci.com/user/workers/container-based-infrastructure/
# - ls /var/log/*mysql*
# - cat /var/log/mysql.err
# - cat /var/log/mysql.log
# - cat /var/log/mysql/error.log
# - cat /var/log/mysql/slow.log
# - ls /var/log/postgresql
# - cat /var/log/postgresql/postgresql*
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
Version 2, June 1991
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
Preamble
The licenses for most software are designed to take away your
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
Foundation's software and to any other program whose authors commit to
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.
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
modification follow.
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
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
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
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
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
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
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
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 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
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
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
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
@ -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
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.>
Copyright (C) <year> <name of author>
{description}
Copyright (C) {year} {fullname}
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
@ -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
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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
`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
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
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.

View File

@ -11,6 +11,14 @@
# Shimmie
[![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
websites, this is the version to use.
@ -21,18 +29,27 @@ check out one of the versioned branches.
# Requirements
- 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
# 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
3. Visit the folder with a web browser
4. Enter the location of the database
5. Click "install". Hopefully you'll end up at the welcome screen; if
3. Create a blank database
4. Visit the folder with a web browser
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~
# 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
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.
If you give shimmie to someone else, you have to give them the source (which should be easy, as PHP
is an interpreted language...). If you want to add customisations to your own
site, then those customisations belong to you, and you can do what you want
with them.
If you give shimmie to someone else, you have to give them the source (which
should be easy, as PHP is an interpreted language...). If you want to add
customisations to your own site, then those customisations belong to you,
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/util.inc.php";
require_once "lib/context.php";
require_once "vendor/autoload.php";
require_once "core/imageboard.pack.php";
// set up and purify the environment
_version_check();
@ -16,17 +18,17 @@ _sanitise_environment();
// load base files
ctx_log_start("Opening files");
$files = array_merge(
$_shm_files = array_merge(
zglob("core/*.php"),
zglob("ext/{".ENABLED_EXTS."}/main.php")
);
foreach($files as $filename) {
if(basename($filename)[0] != "_") {
require_once $filename;
foreach($_shm_files as $_shm_filename) {
if(basename($_shm_filename)[0] != "_") {
require_once $_shm_filename;
}
}
unset($files);
unset($filename);
unset($_shm_files);
unset($_shm_filename);
ctx_log_endok();
// connect to the database

View File

@ -54,7 +54,7 @@ class BaseThemelet {
$h_view_link = make_link('post/view/'.$i_id);
$h_thumb_link = $image->get_thumb_link();
$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
if(!isset($extArr[$image->ext])){
@ -67,7 +67,7 @@ class BaseThemelet {
$custom_classes = "";
if(class_exists("Relationships")){
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'>".

View File

@ -44,6 +44,14 @@ class Block {
*/
public $id;
/**
* Should this block count as content for the sake of
* the 404 handler
*
* @var boolean
*/
public $is_content = true;
/**
* Construct a block.
*
@ -58,7 +66,11 @@ class Block {
$this->body = $body;
$this->section = $section;
$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
*/
abstract class BaseConfig implements Config {
var $values = array();
public $values = array();
/**
* @param string $name
@ -366,8 +366,8 @@ class StaticConfig extends BaseConfig {
* \endcode
*/
class DatabaseConfig extends BaseConfig {
/** @var \Database|null */
var $database = null;
/** @var Database */
private $database = null;
/**
* Load the config table from a database.

View File

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

View File

@ -85,22 +85,16 @@ abstract class Extension {
/** @var array which DBs this ext supports (blank for 'all') */
protected $db_support = [];
/** this theme's Themelet object */
/** @var Themelet this theme's Themelet object */
public $theme;
/** @private */
var $_child;
// 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);
public function __construct() {
$this->theme = $this->get_theme_object(get_called_class());
}
/**
* @return boolean
*/
public function is_live() {
global $database;
return (
@ -112,21 +106,21 @@ abstract class Extension {
/**
* Find the theme object for a given extension.
*
* @param Extension $class
* @param bool $fatal
* @return bool
* @param string $base
* @return Themelet
*/
private function get_theme_object(Extension $class, $fatal=true) {
$base = get_class($class);
if(class_exists('Custom'.$base.'Theme')) {
$class = 'Custom'.$base.'Theme';
return new $class();
private function get_theme_object($base) {
$custom = 'Custom'.$base.'Theme';
$normal = $base.'Theme';
if(class_exists($custom)) {
return new $custom();
}
elseif ($fatal || class_exists($base.'Theme')) {
$class = $base.'Theme';
return new $class();
} else {
return false;
elseif(class_exists($normal)) {
return new $normal();
}
else {
return null;
}
}
@ -183,7 +177,7 @@ abstract class DataHandlerExtension extends Extension {
$supported_ext = $this->supported_ext($event->type);
$check_contents = $this->check_contents($event->tmpname);
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));
/* 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() {
$data = '';
ksort($this->html_headers);
foreach ($this->html_headers as $line) {
$data .= $line . "\n";
$data .= "\t\t" . $line . "\n";
}
return $data;
}
@ -289,7 +290,7 @@ class Page {
# header("Cache-control: no-cache");
# 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, "/");
}
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='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;
foreach(zglob("data/config/*") as $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;
foreach(array_merge(zglob("lib/*.css"), zglob("ext/*/style.css"), zglob("themes/$theme_name/style.css")) as $css) {
$css_files[] = $css;
$css_files = array_merge(zglob("lib/shimmie.css"), zglob("ext/{".ENABLED_EXTS."}/style.css"), zglob("themes/$theme_name/style.css"));
foreach($css_files as $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)) {
$css_data = "";
foreach($css_files as $file) {
@ -360,15 +383,32 @@ class Page {
}
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;
foreach(array_merge(zglob("lib/*.js"), zglob("ext/*/script.js"), zglob("themes/$theme_name/script.js")) as $js) {
$js_files[] = $js;
$js_files = array_merge(zglob("lib/shimmie.js"), zglob("ext/{".ENABLED_EXTS."}/script.js"), zglob("themes/$theme_name/script.js"));
foreach($js_files as $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)) {
$js_data = "";
foreach($js_files as $file) {
@ -376,7 +416,7 @@ class Page {
}
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("SEARCH_ACCEL", false); // boolean use search accelerator
_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("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

View File

@ -1,7 +1,10 @@
<?php
require_once "lib/password.php";
/** @private */
/**
* @private
* @param mixed $row
* @return User
*/
function _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 */
@ -222,9 +212,15 @@ class User {
*/
public function set_password(/*string*/ $password) {
global $database;
$this->passhash = password_hash($password, PASSWORD_BCRYPT);
$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);
$hash = password_hash($password, PASSWORD_BCRYPT);
if(is_string($hash)) {
$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
require_once "lib/recaptchalib.php";
require_once "lib/securimage/securimage.php";
require_once "lib/context.php";
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
@ -10,17 +8,27 @@ require_once "lib/context.php";
/**
* Make some data safe for printing into HTML
*
* @param $input
* @param string $input
* @return string
*/
function html_escape($input) {
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
*
* @param $input
* @param string $input
* @return int
*/
function int_escape($input) {
@ -34,7 +42,7 @@ function int_escape($input) {
/**
* Make sure some data is safe to be used in URL context
*
* @param $input
* @param string $input
* @return string
*/
function url_escape($input) {
@ -69,7 +77,7 @@ function url_escape($input) {
/**
* Make sure some data is safe to be used in SQL context
*
* @param $input
* @param string $input
* @return string
*/
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
*
* @param $input
* @return bool
* @param mixed $input
* @return boolean
*/
function bool_escape($input) {
/*
@ -117,7 +125,7 @@ function bool_escape($input) {
* Some functions require a callback function for escaping,
* but we might not want to alter the data
*
* @param $input
* @param string $input
* @return string
*/
function no_escape($input) {
@ -168,8 +176,15 @@ function xml_tag($name, $attrs=array(), $children=array()) {
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="...") {
// return with no change if string is shorter than $limit
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
*
* @param $limit
* @param string|integer $limit
* @return int
*/
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
*
* @param $int
* @param integer $int
* @return string
*/
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
*
* @param $date
* @param string $date
* @param bool $html
* @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 )
*
* @param $dateTime
* @param string $dateTime
* @return bool
*/
function isValidDateTime($dateTime) {
@ -268,7 +283,7 @@ function isValidDateTime($dateTime) {
/**
* Check if a given string is a valid date. ( Format: yyyy-mm-dd )
*
* @param $date
* @param string $date
* @return bool
*/
function isValidDate($date) {
@ -282,6 +297,9 @@ function isValidDate($date) {
return false;
}
/**
* @param string[] $inputs
*/
function validate_input($inputs) {
$outputs = array();
@ -376,8 +394,8 @@ function validate_input($inputs) {
*
* FIXME: also check that IP ban ext is installed
*
* @param $ip
* @param $ban_reason
* @param string $ip
* @param string $ban_reason
* @return string
*/
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.
*
* @param $haystack String to examine.
* @param $needle String to look for.
* @param string $haystack String to examine.
* @param string $needle String to look for.
* @return bool
*/
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.
*
* @param $haystack String to examine.
* @param $needle String to look for.
* @param string $haystack String to examine.
* @param string $needle String to look for.
* @return bool
*/
function endsWith(/*string*/ $haystack, /*string*/ $needle) {
@ -414,7 +432,6 @@ function endsWith(/*string*/ $haystack, /*string*/ $needle) {
return (substr($haystack, $start) === $needle);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* HTML Generation *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@ -434,7 +451,10 @@ function make_link($page=null, $query=null) {
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"]);
}
else {
@ -535,7 +555,15 @@ function make_http(/*string*/ $link) {
*/
function make_form($target, $method="POST", $multipart=False, $form_id="", $onsubmit="") {
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 .'"';
if($multipart) {
$extra .= " enctype='multipart/form-data'";
@ -543,7 +571,7 @@ function make_form($target, $method="POST", $multipart=False, $form_id="", $onsu
if($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 *
@ -608,14 +662,12 @@ function captcha_get_html() {
if($user->is_anonymous() && $config->get_bool("comment_captcha")) {
$r_publickey = $config->get_string("api_recaptcha_pubkey");
if(!empty($r_publickey)) {
$captcha = recaptcha_get_html($r_publickey);
}
else {
$captcha = "
<div class=\"g-recaptcha\" data-sitekey=\"{$r_publickey}\"></div>
<script type=\"text/javascript\" src=\"https://www.google.com/recaptcha/api.js\"></script>";
} else {
session_start();
//$securimg = new 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='' />";
$captcha = Securimage::getCaptchaHtml(['securimage_path' => './vendor/dapphp/securimage/']);
}
}
return $captcha;
@ -632,22 +684,18 @@ function captcha_check() {
if($user->is_anonymous() && $config->get_bool("comment_captcha")) {
$r_privatekey = $config->get_string('api_recaptcha_privkey');
if(!empty($r_privatekey)) {
$resp = recaptcha_check_answer(
$r_privatekey,
$_SERVER["REMOTE_ADDR"],
$_POST["recaptcha_challenge_field"],
$_POST["recaptcha_response_field"]
);
$recaptcha = new \ReCaptcha\ReCaptcha($r_privatekey);
$resp = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
if(!$resp->is_valid) {
log_info("core", "Captcha failed (ReCaptcha): " . $resp->error);
if(!$resp->isSuccess()) {
log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes()));
return false;
}
}
else {
session_start();
$securimg = new Securimage();
if($securimg->check($_POST['code']) == false) {
if($securimg->check($_POST['captcha_code']) === false) {
log_info("core", "Captcha failed (Securimage)");
return false;
}
@ -678,7 +726,7 @@ function is_https_enabled() {
* from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Schönknecht
* and released under the 'Simplified BSD License'.
*
* @param string &$file File path
* @param string $file File path
* @param string $ext
* @param bool $list
* @return string
@ -704,7 +752,7 @@ function getMimeType($file, $ext="", $list=false) {
'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]; }
@ -799,7 +847,7 @@ function get_memory_limit() {
// Shimmie wants more memory than what PHP is currently set for.
// 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 */
return $memory;
}
@ -918,6 +966,17 @@ function data_path($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 $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
/**
* @param string $raw_headers
* @return string[]
*/
function http_parse_headers ($raw_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.
*
* @param array $headers
* @param mixed $name
* @return mixed
* @param string $name
* @return string|bool
*/
function findHeader ($headers, $name) {
if (!is_array($headers)) {
@ -1111,10 +1175,40 @@ function log_msg(/*string*/ $section, /*int*/ $priority, /*string*/ $message, $f
}
// 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);}
/**
* @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);}
/**
* @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);}
/**
* @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);}
/**
* @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);}
@ -1145,8 +1239,8 @@ function get_request_id() {
/**
* Remove an item from an array
*
* @param $array
* @param $to_remove
* @param array $array
* @param mixed $to_remove
* @return array
*/
function array_remove($array, $to_remove) {
@ -1165,8 +1259,8 @@ function array_remove($array, $to_remove) {
*
* Also removes duplicate values from the array.
*
* @param $array
* @param $element
* @param array $array
* @param mixed $element
* @return array
*/
function array_add($array, $element) {
@ -1180,7 +1274,7 @@ function array_add($array, $element) {
/**
* Return the unique elements of an array, case insensitively
*
* @param $array
* @param array $array
* @return array
*/
function array_iunique($array) {
@ -1204,8 +1298,8 @@ function array_iunique($array) {
*
* from http://uk.php.net/network
*
* @param $IP
* @param $CIDR
* @param string $IP
* @param string $CIDR
* @return bool
*/
function ip_in_range($IP, $CIDR) {
@ -1344,7 +1438,10 @@ function list_files(/*string*/ $base, $_sub_dir="") {
return $file_list;
}
/**
* @param string $path
* @return string
*/
function path_to_tags($path) {
$matches = array();
if(preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", basename($path), $matches)) {
@ -1400,7 +1497,6 @@ function _set_event_listeners() {
elseif(is_subclass_of($class, "Extension")) {
/** @var Extension $extension */
$extension = new $class();
$extension->i_am($extension);
// skip extensions which don't support our current database
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) {
$p = "<"."?php\n";
@ -1427,7 +1527,6 @@ function _dump_event_listeners($event_listeners, $path) {
if($rclass->isAbstract()) {}
elseif(is_subclass_of($class, "Extension")) {
$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
*/
function ext_is_live($ext_name) {
@ -1552,14 +1651,17 @@ function score_assert_handler($file, $line, $code, $desc = null) {
/** @privatesection */
function _version_check() {
$min_version = "5.4.8";
if(version_compare(PHP_VERSION, $min_version) == -1) {
print "
Currently SCore Engine doesn't support versions of PHP lower than $min_version --
if your web host is running an older version, they are dangerously out of
if(MIN_PHP_VERSION)
{
if(version_compare(phpversion(), MIN_PHP_VERSION, ">=") === FALSE) {
print "
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.
";
exit;
exit;
}
}
}
@ -1690,6 +1792,9 @@ function _get_user() {
return $user;
}
/**
* @return string
*/
function _get_query() {
return @$_POST["q"]?:@$_GET["q"];
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -47,6 +47,7 @@ class ArrowkeyNavigation extends Extension {
(function($){
$(document).keyup(function(e) {
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}'; }
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)
VALUES (?, ?, ?, now(), now())
", 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);
}
/**
* 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) {
$comment = strtolower($comment);
@ -105,6 +111,9 @@ xanax
}
}
/**
* @return string[]
*/
private function get_words() {
global $config;
$words = array();

View File

@ -40,8 +40,11 @@ class Blocks extends Extension {
$database->cache->set("blocks", $blocks, 600);
}
foreach($blocks as $block) {
if(fnmatch($block['pages'], implode("/", $event->args))) {
$page->add_block(new Block($block['title'], $block['content'], $block['area'], $block['priority']));
$path = implode("/", $event->args);
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").slideToggle("slow", function() {
if($(".shm-blotter2").is(":hidden")) {
$.cookie("ui-blotter2-hidden", 'true', {path: '/'});
Cookies.set("ui-blotter2-hidden", 'true');
}
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();
}
});

View File

@ -62,7 +62,7 @@ class BlotterTheme extends Themelet {
if($entries[$i]['important'] == 'Y') { $important = 'Y'; } else { $important = 'N'; }
// Add the new table row(s)
$table_rows .=
$table_rows .=
"<tr>
<td>$entry_date</td>
<td>$entry_text</td>
@ -114,7 +114,7 @@ class BlotterTheme extends Themelet {
$i_open = "<font color='#{$i_color}'>";
$i_close="</font>";
}
$html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}<br /><br />";
$html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}<br /><br />";
}
$html .= "</pre>";
return $html;
@ -139,9 +139,9 @@ class BlotterTheme extends Themelet {
$entry_text = $entries[$i]['entry_text'];
if($entries[$i]['important'] == 'Y') {
$i_open = "<font color='#{$i_color}'>";
$i_close="</font>";
$i_close="</font>";
}
$entries_list .= "<li>{$i_open}{$clean_date} - {$entry_text}{$i_close}</li>";
$entries_list .= "<li>{$i_open}{$clean_date} - {$entry_text}{$i_close}</li>";
}
$pos_break = "";
@ -149,7 +149,7 @@ class BlotterTheme extends Themelet {
if($position === "left") {
$pos_break = "<br />";
$pos_align = "";
$pos_align = "";
}
if(count($entries) === 0) {
@ -176,4 +176,3 @@ class BlotterTheme extends Themelet {
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);
$bae = new BulkAddEvent($_POST['dir']);
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->display_upload_results($page);

View File

@ -1,7 +1,7 @@
<?php
class BulkAddTheme extends Themelet {
var $messages = array();
private $messages = array();
/*
* Show a standard page for results to be put into
@ -12,9 +12,9 @@ class BulkAddTheme extends Themelet {
$page->add_block(new NavBlock());
$html = "";
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['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = $tags;
$metadata['tags'] = Tag::explode($tags);
$metadata['source'] = $source;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);

View File

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

View File

@ -10,15 +10,23 @@
$admin = loggedIn();
$log = 1;
if (isset($_GET['log']))
{
$log = $_GET['log'];
}
if (isset($_POST['log']))
{
$log = $_POST['log'];
}
if (!isset($log))
if (filter_var($log, FILTER_VALIDATE_INT) === false)
{
$log = 1;
}
$ys = ys($log);
$posts = $ys->posts();
@ -132,4 +140,4 @@ if (isset($_POST['p'])) {
<a id="to-top" href="#top">Back to top</a>
</div>
</body>
</html>
</html>

View File

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

View File

@ -89,7 +89,10 @@
global $yShout, $prefs;
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;
return new YShout($log, loggedIn());

View File

@ -9,7 +9,7 @@
* 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 {
/** @var int */
@ -53,9 +53,9 @@ class CommentDeletionEvent extends Event {
class CommentPostingException extends SCoreException {}
class Comment {
var $owner, $owner_id, $owner_name, $owner_email, $owner_class;
var $comment, $comment_id;
var $image_id, $poster_ip, $posted;
public $owner, $owner_id, $owner_name, $owner_email, $owner_class;
public $comment, $comment_id;
public $image_id, $poster_ip, $posted;
public function __construct($row) {
$this->owner = null;
@ -94,7 +94,7 @@ class Comment {
class CommentList extends Extension {
/** @var CommentListTheme $theme */
var $theme;
public $theme;
public function onInitExt(InitExtEvent $event) {
global $config, $database;

View File

@ -1,11 +1,11 @@
<?php
class CommentListTheme extends Themelet {
var $comments_shown = 0;
var $show_anon_id = false;
var $anon_id = 1;
var $anon_cid = 0;
var $anon_map = array();
var $ct = null;
private $comments_shown = 0;
private $show_anon_id = false;
private $anon_id = 1;
private $anon_cid = 0;
private $anon_map = array();
private $ct = null;
private function get_anon_colour($ip) {
if(is_null($this->ct)) {
@ -259,8 +259,6 @@ class CommentListTheme extends Themelet {
else {
$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" : "");
if($trim) {
@ -276,13 +274,18 @@ class CommentListTheme extends Themelet {
if(!empty($comment->owner_email)) {
$hash = md5(strtolower($comment->owner_email));
$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_ip = $user->can("view_ip") ? "<br>".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : "";
$h_del = $user->can("delete_comment") ?
' - <a onclick="return confirm(\'Delete comment by '.$h_name.':\\n'.$stripped_nonl.'\');" '.
'href="'.make_link('comment/delete/'.$i_comment_id.'/'.$i_image_id).'">Del</a>' : '';
$h_del = "";
if ($user->can("delete_comment")) {
$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 = "
<div class=\"comment $hb\" id=\"c$i_comment_id\">
<div class=\"info\">

View File

@ -213,7 +213,7 @@ class CronUploader extends Extension {
/**
* Returns amount of files & total size of dir.
* @param $path string
* @param string $path directory name to scan
* @return multitype:number
*/
function scan_dir($path){
@ -234,7 +234,7 @@ class CronUploader extends Extension {
/**
* 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
*/
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.
*
* @param string $tmpname
* @param string $filename
* @param string $tags
*/
private function add_image($tmpname, $filename, $tags) {
assert ( file_exists ( $tmpname ) );
assert('is_string($tags)');
$pathinfo = pathinfo ( $filename );
if (! array_key_exists ( 'extension', $pathinfo )) {
@ -315,7 +320,7 @@ class CronUploader extends Extension {
$metadata = array();
$metadata ['filename'] = $pathinfo ['basename'];
$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;
$event = new DataUploadEvent ( $tmpname, $metadata );
send_event ( $event );
@ -329,7 +334,7 @@ class CronUploader extends Extension {
// Set tags
$img = Image::by_id($event->image_id);
$img->set_tags($tags);
$img->set_tags(Tag::explode($tags));
}
private function generate_image_queue($base = "", $subdir = "") {

View File

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

View File

@ -1,37 +1,42 @@
<?php
class ExtManagerTheme extends Themelet {
/**
* @param Page $page
* @param ExtensionInfo[] $extensions
* @param bool $editable
*/
public function display_table(Page $page, /*array*/ $extensions, /*bool*/ $editable) {
$h_en = $editable ? "<th>Enabled</th>" : "";
$html = "
".make_form(make_link("ext_manager/set"))."
<script type='text/javascript'>
$(document).ready(function() {
$(\"#extensions\").tablesorter();
});
</script>
<table id='extensions' class='zebra'>
<table id='extensions' class='zebra sortable'>
<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>
<tbody>
";
foreach($extensions as $extension) {
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);
if($extension->enabled === TRUE) $h_enabled = " checked='checked'";
else if($extension->enabled === FALSE) $h_enabled = "";
else $h_enabled = " disabled checked='checked'";
$h_link = make_link("ext_doc/".url_escape($extension->ext_name));
$h_link = make_link("ext_doc/".url_escape($extension->ext_name));
$h_enabled = ($extension->enabled === TRUE ? " checked='checked'" : ($extension->enabled === FALSE ? "" : " disabled checked='checked'"));
$h_enabled_box = $editable ? "<td><input type='checkbox' name='ext_".html_escape($extension->ext_name)."'$h_enabled></td>" : "";
$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 .= "
<tr>
$h_en
<td><a href='$h_link'>$h_name</a></td>
<td style='text-align: left;'>$h_description</td>
<tr data-ext='{$extension->ext_name}'>
{$h_enabled_box}
<td>{$h_name}</td>
<td>{$h_docs}</td>
<td style='text-align: left;'>{$h_description}</td>
</tr>";
}
$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) {
global $page, $user;
if($event->page_matches("forum")) {
switch($event->get_arg(0)) {
case "index":
{
$this->show_last_threads($page, $event, $user->is_admin());
if(!$user->is_anonymous()) $this->theme->display_new_thread_composer($page);
break;
}
case "view":
{
$threadID = int_escape($event->get_arg(1));
$pageNumber = int_escape($event->get_arg(2));
list($errors) = $this->sanity_check_viewed_thread($threadID);
global $page, $user;
if($event->page_matches("forum")) {
switch($event->get_arg(0)) {
case "index":
$this->show_last_threads($page, $event, $user->is_admin());
if(!$user->is_anonymous()) $this->theme->display_new_thread_composer($page);
break;
case "view":
$threadID = int_escape($event->get_arg(1));
$pageNumber = int_escape($event->get_arg(2));
list($errors) = $this->sanity_check_viewed_thread($threadID);
if($errors!=null)
{
$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)
{
$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();
{
$this->theme->display_error(500, "Error", $errors);
break;
}
if($errors!=null)
{
$this->theme->display_error(500, "Error", $errors);
break;
}
$newThreadID = $this->save_new_thread($user);
$this->save_new_post($newThreadID, $user);
$redirectTo = "forum/view/".$newThreadID."/1";
}
$newThreadID = $this->save_new_thread($user);
$this->save_new_post($newThreadID, $user);
$redirectTo = "forum/view/".$newThreadID."/1";
}
$page->set_mode("redirect");
$page->set_redirect(make_link($redirectTo));
$page->set_mode("redirect");
$page->set_redirect(make_link($redirectTo));
break;
case "delete":
$threadID = int_escape($event->get_arg(1));
$postID = int_escape($event->get_arg(2));
break;
}
case "delete":
$threadID = int_escape($event->get_arg(1));
$postID = int_escape($event->get_arg(2));
if ($user->is_admin()) {$this->delete_post($postID);}
if ($user->is_admin()) {$this->delete_post($postID);}
$page->set_mode("redirect");
$page->set_redirect(make_link("forum/view/".$threadID));
break;
case "nuke":
$threadID = int_escape($event->get_arg(1));
$page->set_mode("redirect");
$page->set_redirect(make_link("forum/view/".$threadID));
break;
case "nuke":
$threadID = int_escape($event->get_arg(1));
if ($user->is_admin())
$this->delete_thread($threadID);
if ($user->is_admin())
$this->delete_thread($threadID);
$page->set_mode("redirect");
$page->set_redirect(make_link("forum/index"));
break;
case "answer":
$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();
$page->set_mode("redirect");
$page->set_redirect(make_link("forum/index"));
break;
case "answer":
$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 ($errors!=null)
{
$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;
if ($errors!=null)
{
$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;
}
}
}
}
/**
* @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) {
$n = 0;
foreach($blocks as $block) {
if($block->section == "main") $n++; // more hax.
}
if(ext_is_live("Chatbox")) {
$n--; // even more hax.
if($block->section == "main" && $block->is_content) $n++; // more hax.
}
return $n;
}

View File

@ -35,8 +35,10 @@ class ArchiveFileHandler extends Extension {
exec($cmd);
$results = add_dir($tmpdir);
if(count($results) > 0) {
// FIXME no theme?
$this->theme->add_status("Adding files", $results);
// Not all themes have the add_status() method, so need to check before calling.
if (method_exists($this->theme, "add_status")) {
$this->theme->add_status("Adding files", $results);
}
}
deltree($tmpdir);
$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->filename = $metadata['filename'];
$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'];
$info = getimagesize($filename);

View File

@ -10,7 +10,7 @@ class IcoFileHandler extends Extension {
if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
$hash = $event->hash;
$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));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
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
* @return bool
@ -67,38 +53,40 @@ class IcoFileHandler extends Extension {
$image = new Image();
$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);
$image->width = $subheader['width'];
$image->height = $subheader['height'];
$width = $subheader['width'];
$height = $subheader['height'];
$image->width = $width == 0 ? 256 : $width;
$image->height = $height == 0 ? 256 : $height;
$image->filesize = $metadata['size'];
$image->hash = $metadata['hash'];
$image->filename = $metadata['filename'];
$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'];
return $image;
}
/**
* @param $file
* @param string $file
* @return bool
*/
private function check_contents($file) {
if(!file_exists($file)) return false;
$fp = fopen($file, "r");
$header = unpack("snull/stype/scount", fread($fp, 6));
$header = unpack("Snull/Stype/Scount", fread($fp, 6));
fclose($fp);
return ($header['null'] == 0 && ($header['type'] == 0 || $header['type'] == 1));
}
/**
* @param $hash
* @param string $hash
* @return bool
*/
private function create_thumb($hash) {

View File

@ -4,7 +4,6 @@ class IcoHandlerTest extends ShimmiePHPUnitTestCase {
$this->log_in_as_user();
$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("get_ico/$image_id"); // test for no crash
# FIXME: test that the thumb works
# FIXME: test that it gets displayed properly

View File

@ -2,9 +2,10 @@
class IcoFileHandlerTheme extends Themelet {
public function display_image(Page $page, Image $image) {
$ilink = make_link("get_ico/{$image->id}/{$image->id}.ico");
$ilink = $image->get_image_link();
$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));
}

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
*/
protected function create_image_from_data($filename, $metadata) {
global $config;
$image = new Image();
// FIXME: need more flash format specs :|
$image->width = 0;
$image->height = 0;
//NOTE: No need to set width/height as we don't use it.
$image->width = 1;
$image->height = 1;
$image->filesize = $metadata['size'];
$image->hash = $metadata['hash'];
//Cheat by using the filename to store artist/title if available
require_once('lib/getid3/getid3/getid3.php');
$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'];
}
//Filename is renamed to "artist - title.mp3" when the user requests download by using the download attribute & jsmediatags.js
$image->filename = $metadata['filename'];
$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'];
return $image;
@ -66,15 +54,15 @@ class MP3FileHandler extends DataHandlerExtension {
* @return bool
*/
protected function check_contents($file) {
$success = FALSE;
if (file_exists($file)) {
require_once('lib/getid3/getid3/getid3.php');
$getID3 = new getID3;
$ThisFileInfo = $getID3->analyze($file, TRUE);
if (isset($ThisFileInfo['fileformat']) && $ThisFileInfo['fileformat'] == "mp3") {
return TRUE;
}
$mimeType = mime_content_type($file);
$success = ($mimeType == 'audio/mpeg');
}
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();
$fname = url_escape($image->filename); //Most of the time this will be the title/artist of the song.
$html = "
<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'
codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0'
width='400' height='15'>
<param name='movie' value='$data_href/ext/handle_mp3/xspf_player_slim.swf?song_url=$ilink'/>
<param name='quality' value='high' />
<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'
width='400' height='15'
type='application/x-shockwave-flash'></embed>
</object>
<p><a href='$ilink'>Download</a>";
<audio controls class='shm-main-image audio_image' id='main_image' alt='main image'>
<source src=\"$ilink\" type=\"audio/mpeg\">
Your browser does not support the audio element.
</audio>
<p>Title: <span id='audio-title'>???</span> | Artist: <span id='audio-artist'>???</span></p>
<script>
$('#main_image').prop('volume', 0.25);
var jsmediatags = window.jsmediatags;
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));
}
}

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->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->ext = (($pos = strpos($metadata['extension'],'?')) !== false) ? substr($metadata['extension'],0,$pos) : $metadata['extension'];
$image->tag_array = Tag::explode($metadata['tags']);
$image->source = $metadata['source'];
$image->ext = (($pos = strpos($metadata['extension'],'?')) !== false) ? substr($metadata['extension'],0,$pos) : $metadata['extension'];
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
$image->source = $metadata['source'];
return $image;
}

View File

@ -1,5 +1,7 @@
$(function() {
function zoom(zoom_type) {
function zoom(zoom_type, save_cookie) {
save_cookie = save_cookie === undefined ? true : save_cookie;
var img = $('.shm-main-image');
if(zoom_type == "full") {
@ -21,21 +23,28 @@ $(function() {
$(".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) {
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) {
switch($.cookie("ui-image-zoom")) {
$("img.shm-main-image").click(function(e) {
switch(Cookies.get("ui-image-zoom")) {
case "full": zoom("width"); break;
default: zoom("full"); break;
}
});
if($.cookie("ui-image-zoom")) {
zoom($.cookie("ui-image-zoom"));
if(Cookies.get("ui-image-zoom")) {
zoom(Cookies.get("ui-image-zoom"));
}
});

View File

@ -3,14 +3,14 @@
* Name: Handle SVG
* Author: Shish <webmaster@shishnet.org>
* 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 {
public function onDataUpload(DataUploadEvent $event) {
if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
$hash = $event->hash;
if(!move_upload_to_archive($event)) return;
move_upload_to_archive($event);
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data(warehouse_path("images", $hash), $event->metadata);
if(is_null($image)) {
@ -75,7 +75,7 @@ class SVGFileHandler extends Extension {
$image->hash = $metadata['hash'];
$image->filename = $metadata['filename'];
$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'];
return $image;
@ -101,6 +101,10 @@ class MiniSVGParser {
/** @var int */
public $height=0;
/** @var int */
private $xml_depth=0;
/** @param string $file */
function __construct($file) {
$xml_parser = xml_parser_create();
xml_set_element_handler($xml_parser, array($this, "startElement"), array($this, "endElement"));
@ -109,13 +113,15 @@ class MiniSVGParser {
}
function startElement($parser, $name, $attrs) {
if($name == "SVG") {
if($name == "SVG" && $this->xml_depth == 0) {
$this->width = int_escape($attrs["WIDTH"]);
$this->height = int_escape($attrs["HEIGHT"]);
}
$this->xml_depth++;
}
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 = $image->get_image_link();
$html = "
<object data='$ilink' type='image/svg+xml' width='{$image->width}' height='{$image->height}'>
<embed src='$ilink' type='image/svg+xml' width='{$image->width}' height='{$image->height}' />
</object>
<img src='$ilink' id='main_image' class='shm-main-image' data-width='{$image->width}' data-height='{$image->height}' />
";
$page->add_block(new Block("Image", $html, "main", 10));
}

View File

@ -2,7 +2,7 @@
/*
* Name: Handle Video
* 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
* Description: Handle FLV, MP4, OGV and WEBM video files.
* Documentation:
@ -12,31 +12,46 @@
* 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.
* 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 {
public function onInitExt(InitExtEvent $event) {
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.
//
// 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);
if($config->get_int("ext_handle_video_version") < 1) {
if($ffmpeg = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffmpeg')) {
//ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static
if(is_executable(strtok($ffmpeg, PHP_EOL))) {
$config->set_default_string('video_thumb_engine', 'ffmpeg');
$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) {
//global $config;
$thumbers = array();
$thumbers['None'] = "static";
$thumbers['ffmpeg'] = "ffmpeg";
$thumbers = array(
'None' => 'static',
'ffmpeg' => 'ffmpeg'
);
$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: ");
$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)
{
$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
{
$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);
@ -119,34 +140,28 @@ class VideoFileHandler extends DataHandlerExtension {
/**
* @param string $filename
* @param mixed[] $metadata
* @return Image|null
* @return Image
*/
protected function create_image_from_data($filename, $metadata) {
$image = new Image();
require_once('lib/getid3/getid3/getid3.php');
$getID3 = new getID3;
$ThisFileInfo = $getID3->analyze($filename);
//NOTE: No need to set width/height as we don't use it.
$image->width = 1;
$image->height = 1;
if (isset($ThisFileInfo['video']['resolution_x']) && isset($ThisFileInfo['video']['resolution_y'])) {
$image->width = $ThisFileInfo['video']['resolution_x'];
$image->height = $ThisFileInfo['video']['resolution_y'];
} else {
$image->width = 0;
$image->height = 0;
}
switch ($ThisFileInfo['mime_type']) {
switch (mime_content_type($filename)) {
case "video/webm":
$image->ext = "webm";
break;
case "video/quicktime":
case "video/mp4":
$image->ext = "mp4";
break;
case "application/ogg":
case "video/ogg":
$image->ext = "ogv";
break;
case "video/flv":
$image->ext = "flv";
break;
case "video/x-flv":
$image->ext = "flv";
break;
@ -155,7 +170,7 @@ class VideoFileHandler extends DataHandlerExtension {
$image->filesize = $metadata['size'];
$image->hash = $metadata['hash'];
$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'];
return $image;
@ -166,20 +181,20 @@ class VideoFileHandler extends DataHandlerExtension {
* @return bool
*/
protected function check_contents($file) {
$success = FALSE;
if (file_exists($file)) {
require_once('lib/getid3/getid3/getid3.php');
$getID3 = new getID3;
$ThisFileInfo = $getID3->analyze($file);
if (isset($ThisFileInfo['mime_type']) && (
$ThisFileInfo['mime_type'] == "video/webm" ||
$ThisFileInfo['mime_type'] == "video/quicktime" ||
$ThisFileInfo['mime_type'] == "application/ogg" ||
$ThisFileInfo['mime_type'] == 'video/x-flv')
) {
return TRUE;
}
$mimeType = mime_content_type($file);
$success = in_array($mimeType, [
'video/webm',
'video/mp4',
'video/ogg',
'video/flv',
'video/x-flv'
]);
}
return FALSE;
return $success;
}
}

View File

@ -2,47 +2,62 @@
class VideoFileHandlerTheme extends Themelet {
public function display_image(Page $page, Image $image) {
$data_href = get_base_href();
global $config;
$ilink = $image->get_image_link();
$thumb_url = make_http($image->get_thumb_link()); //used as fallback image
$ext = strtolower($image->get_ext());
if ($ext == "mp4") {
$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'>
if( navigator.userAgent.match(/Firefox/i) ||
navigator.userAgent.match(/Opera/i) ||
(navigator.userAgent.match(/MSIE/i) && parseFloat(navigator.appVersion.split('MSIE')[1]) < 9)){
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>\");
}
else {
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>\");
}
</script>
<noscript>Javascript appears to be disabled. Please enable it and try again.</noscript>";
} elseif ($ext == "flv") {
$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 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>";
} elseif ($ext == "ogv") {
$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/>
<video controls autoplay loop>
<source src='" . make_link("/image/" . $image->id) . "' type='video/ogg' />
</video>";
} elseif ($ext == "webm") {
$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]-->";
$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/>
<video controls autoplay loop>
<source src='" . make_link("/image/" . $image->id) . "' type='video/webm' />
</video>";
}
else {
$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');
$html = "Video not playing? <a href='$ilink'>Click here</a> to download the file.<br/>";
//Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
$supportedExts = ['mp4' => 'video/mp4', 'm4v' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm', 'flv' => 'video/flv'];
if(array_key_exists($ext, $supportedExts)) {
//FLV isn't supported by <video>, but it should always fallback to the flash-based method.
if($ext == "webm") {
//Several browsers still lack WebM support sadly: http://caniuse.com/#feat=webm
$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]-->";
}
$html_fallback = "
<object width=\"100%\" height=\"480px\" type=\"application/x-shockwave-flash\" data=\"$player\">
<param name=\"movie\" value=\"$player\" />
<param name=\"allowFullScreen\" value=\"true\" />
<param name=\"wmode\" value=\"opaque\" />
<param name=\"flashVars\" value=\""
. "controls=true"
. "&autoplay=" . ($autoplay ? 'true' : 'false')
. "&poster={$thumb_url}"
. "&file={$full_url}"
. "&loop=" . ($loop ? 'true' : 'false') . "\" />
<img src=\"{$thumb_url}\" />
</object>";
if($ext == "flv") {
//FLV doesn't support <video>.
$html .= $html_fallback;
} else {
$autoplay = ($autoplay ? ' autoplay' : '');
$loop = ($loop ? ' loop' : '');
$html .= "
<video controls class='shm-main-image' id='main_image' alt='main image' {$autoplay} {$loop} style='max-width: 100%'>
<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";
}
$page->add_block(new Block("Video", $html, "main", 10));

View File

@ -49,7 +49,7 @@ class Home extends Extension {
global $config;
$base_href = get_base_href();
$sitename = $config->get_string('title');
$contact_link = $config->get_string('contact_link');
$contact_link = contact_link();
$counter_dir = $config->get_string('home_counter', 'default');
$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 {
public function display_page(Page $page, $sitename, $base_href, $theme_name, $body) {
$page->set_mode("data");
$hh = "";
$page->add_auto_html_headers();
foreach($page->html_headers as $h) {$hh .= $h;}
$hh = $page->get_all_html_headers();
$page->set_data(<<<EOD
<!doctype html>
<html>
<head>
<title>$sitename</title>
@ -14,17 +14,6 @@ class HomeTheme extends Themelet {
<meta name="viewport" content="width=device-width, initial-scale=1">
$hh
</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>
@ -37,7 +26,7 @@ EOD
$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>";
$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 = "
<div class='space' id='search'>
<form action='".make_link("post/list")."' method='GET'>

View File

@ -12,8 +12,10 @@
* An image is being added to the database.
*/
class ImageAdditionEvent extends Event {
var $user;
/** @var \Image */
/** @var User */
public $user;
/** @var Image */
public $image;
/**
@ -30,7 +32,7 @@ class ImageAdditionEvent extends Event {
}
class ImageAdditionException extends SCoreException {
var $error;
public $error;
/**
* @param string $error
@ -374,7 +376,7 @@ class ImageIO extends Extension {
$image->tag_array = array();
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}");
}
}

View File

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

View File

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

View File

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

View File

@ -161,16 +161,16 @@
class SearchTermParseEvent extends Event {
/** @var null|string */
public $term = null;
/** @var null|array */
public $context = null;
/** @var string[] */
public $context = array();
/** @var \Querylet[] */
public $querylets = array();
/**
* @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->context = $context;
}
@ -201,16 +201,16 @@ class SearchTermParseException extends SCoreException {
}
class PostListBuildingEvent extends Event {
/** @var null|array */
public $search_terms = null;
/** @var array */
public $search_terms = array();
/** @var array */
public $parts = array();
/**
* @param array|null $search
* @param string[] $search
*/
public function __construct($search) {
public function __construct(array $search) {
$this->search_terms = $search;
}
@ -225,7 +225,8 @@ class PostListBuildingEvent extends Event {
}
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) {
global $config;
@ -238,7 +239,8 @@ class Index extends Extension {
global $database, $page;
if($event->page_matches("post/list")) {
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)) {
$page->set_mode("redirect");
$page->set_redirect(make_link("post/list/1"));
@ -253,9 +255,9 @@ class Index extends Extension {
$search_terms = $event->get_search_terms();
$page_number = $event->get_page_number();
$page_size = $event->get_page_size();
$count_search_terms = count($search_terms);
try {
#log_debug("index", "Search for ".implode(" ", $search_terms), false, array("terms"=>$search_terms));
$total_pages = Image::count_pages($search_terms);
@ -263,7 +265,7 @@ class Index extends Extension {
$images = $database->cache->get("post-list:$page_number");
if(!$images) {
$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 {
@ -277,7 +279,7 @@ class Index extends Extension {
}
$count_images = count($images);
if($count_search_terms === 0 && $count_images === 0 && $page_number === 1) {
$this->theme->display_intro($page);
send_event(new PostListBuildingEvent($search_terms));
@ -310,7 +312,7 @@ class Index extends Extension {
$event->panel->add_block($sb);
}
public function onImageInfoSet($event) {
public function onImageInfoSet(ImageInfoSetEvent $event) {
global $database;
if(SPEED_HAX) {
$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.
if(preg_match("/^tags([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
$cmp = ltrim($matches[1], ":") ?: "=";
$tags = $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.')'));
$count = $matches[2];
$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)) {
$cmp = preg_replace('/^:/', '=', $matches[1]);
@ -394,4 +405,3 @@ class Index extends Extension {
$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 */
$(function() {
var blocked_tags = ($.cookie("ui-blocked-tags") || "").split(" ");
var blocked_tags = (Cookies.get("ui-blocked-tags") || "").split(" ");
var needs_refresh = false;
for(var i=0; i<blocked_tags.length; i++) {
var tag = blocked_tags[i];
@ -34,9 +34,9 @@ $(function() {
});
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) {
$.cookie("ui-blocked-tags", blocked_tags.toLowerCase(), {path: '/', expires: 365});
Cookies.set("ui-blocked-tags", blocked_tags.toLowerCase(), {expires: 365});
location.reload(true);
}
}

View File

@ -1,5 +1,18 @@
<?php
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() {
$this->get_page('post/list');
$this->assert_title("Welcome to Shimmie ".VERSION);
@ -24,85 +37,168 @@ class IndexTest extends ShimmiePHPUnitTestCase {
$this->assert_title("Shimmie");
$this->get_page('post/list/99999');
$this->assert_title("No Images Found");
# FIXME: test search box
$this->assert_response(404);
}
public function testSearches() {
$this->log_in_as_user();
$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");
$this->log_out();
/* * * * * * * * * * *
* Tag Search *
* * * * * * * * * * */
public function testTagSearchNoResults() {
$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->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->assert_response(200);
$this->assert_title("computer");
$this->assert_no_text("No Images Found");
}
# meta tag, many results
$this->get_page('post/list/size=640x480/1');
$this->assert_title("size=640x480");
$this->assert_no_text("No Images Found");
# 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");
/* * * * * * * * * * *
* Multi-Tag Search *
* * * * * * * * * * */
public function testMultiTagSearchNoResults() {
$image_ids = $this->upload();
# multiple tags, one of which doesn't exist
# (test the "one tag doesn't exist = no hits" path)
$this->get_page('post/list/computer%20asdfasdfwaffle/1');
$this->assert_text("No Images Found");
$this->get_page('post/list/computer asdfasdfwaffle/1');
$this->assert_response(404);
}
# multiple tags, single result; search with one result = direct to image
$this->get_page('post/list/screenshot%20computer/1');
//$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
public function testMultiTagSearchOneResult() {
$image_ids = $this->upload();
$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
$this->get_page('post/list/computer%20-pbx/1');
//$this->assert_title(new PatternExpectation("/^Image $image_id_2: /"));
$this->get_page('post/list/computer -pbx/1');
$this->assert_response(302);
# negative tag alone, should work
# FIXME: known broken in mysql
//$this->get_page('post/list/-pbx/1');
//$this->assert_title(new PatternExpectation("/^Image $image_id_2: /"));
//$this->assert_response(302);
# test various search methods
$this->get_page("post/list/bedroo*/1");
//$this->assert_title(new PatternExpectation("/^Image $image_id_2: /"));
$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");
$this->assert_response(302);
}
}

View File

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

View File

@ -14,7 +14,7 @@
// RemoveIPBanEvent {{{
class RemoveIPBanEvent extends Event {
var $id;
public $id;
public function __construct($id) {
$this->id = $id;
@ -23,9 +23,9 @@ class RemoveIPBanEvent extends Event {
// }}}
// AddIPBanEvent {{{
class AddIPBanEvent extends Event {
var $ip;
var $reason;
var $end;
public $ip;
public $reason;
public $end;
public function __construct(/*string(ip)*/ $ip, /*string*/ $reason, /*string*/ $end) {
$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 "<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)) {
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;
}

View File

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

View File

@ -15,11 +15,11 @@ class LiveFeed extends Extension {
$event->panel->add_block($sb);
}
public function onUserCreation($event) {
public function onUserCreation(UserCreationEvent $event) {
$this->msg("New user created: {$event->username}");
}
public function onImageAddition($event) {
public function onImageAddition(ImageAdditionEvent $event) {
global $user;
$this->msg(
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(
make_http(make_link("post/view/".$event->image->id))." - ".
"tags set to: ".Tag::implode($event->tags)
);
}
public function onCommentPosting($event) {
public function onCommentPosting(CommentPostingEvent $event) {
global $user;
$this->msg(
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");
}
public function get_priority() {return 99;}
/**
* @param string $data
*/
private function msg($data) {
global $config;
assert('is_string($data)');
$host = $config->get_string("livefeed_host", "127.0.0.1:25252");
if(!$host) { return; }

View File

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

View File

@ -26,12 +26,11 @@ class MassTagger extends Extension {
if($event->page_matches("mass_tagger/tag") && $user->is_admin()) {
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();
$neg_tag_array = array();
foreach($tag_array as $new_tag) {
foreach($tags as $new_tag) {
if (strpos($new_tag, '-') === 0)
$neg_tag_array[] = substr($new_tag,1);
else
@ -45,18 +44,19 @@ class MassTagger extends Extension {
if(isset($_POST['setadd']) && $_POST['setadd'] == 'set') {
foreach($images as $image) {
$image->set_tags(Tag::explode($tag));
$image->set_tags($tags);
}
}
else {
foreach($images as $image) {
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);
$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();
if( (string.indexOf(id) == 0) || (string.indexOf(":"+id) > -1) ) {
$(button).css('border', 'none');
$(button).removeClass('mass-tagger-selected');
string = string.replace(id, '');
list.val(string);
}
else {
$(button).css('border', '3px solid blue');
$(button).addClass('mass-tagger-selected');
string += id;
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

@ -30,7 +30,7 @@ class Notes extends Extension {
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE
");
$database->execute("CREATE INDEX notes_image_id_idx ON notes(image_id)", array());
$database->create_table("note_request", "
id SCORE_AIPK,
image_id INTEGER NOT NULL,
@ -40,7 +40,7 @@ class Notes extends Extension {
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE
");
$database->execute("CREATE INDEX note_request_image_id_idx ON note_request(image_id)", array());
$database->create_table("note_histories", "
id SCORE_AIPK,
note_enable INTEGER NOT NULL,
@ -68,110 +68,83 @@ class Notes extends Extension {
log_info("notes", "extension installed");
}
}
public function onPageRequest(PageRequestEvent $event) {
global $page, $user;
if($event->page_matches("note")) {
switch($event->get_arg(0)) {
case "list": //index
{
$this->get_notes_list($event); // This should show images like post/list but i don't know how do that.
break;
}
case "requests": // The same as post/list but only for note_request table.
{
$this->get_notes_requests($event); // This should shouw images like post/list but i don't know how do that.
$this->get_notes_requests($event); // This should show images like post/list but i don't know how do that.
break;
}
case "search":
{
if(!$user->is_anonymous())
$this->theme->search_notes_page($page);
break;
}
case "updated": //Thinking how biuld this function.
{
case "updated": //Thinking how to build this function.
$this->get_histories($event);
break;
}
case "history": //Thinking how biuld this function.
{
case "history": //Thinking how to build this function.
$this->get_history($event);
break;
}
case "revert":
{
$noteID = $event->get_arg(1);
$noteID = $event->get_arg(1);
$reviewID = $event->get_arg(2);
if(!$user->is_anonymous()){
$this->revert_history($noteID, $reviewID);
}
$page->set_mode("redirect");
$page->set_redirect(make_link("note/updated"));
break;
}
case "add_note":
{
if(!$user->is_anonymous())
$this->add_new_note();
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break;
}
case "add_request":
{
if(!$user->is_anonymous())
$this->add_note_request();
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break;
}
case "nuke_notes":
{
if($user->is_admin())
$this->nuke_notes();
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break;
}
case "nuke_requests":
{
if($user->is_admin())
$this->nuke_requests();
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break;
}
case "edit_note":
{
if (!$user->is_anonymous()) {
$this->update_note();
$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":
{
if ($user->is_admin()) {
$this->delete_note();
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
}
break;
}
break;
default:
{
$page->set_mode("redirect");
$page->set_redirect(make_link("note/list"));
break;
}
}
}
}
@ -184,7 +157,7 @@ class Notes extends Extension {
global $page, $user;
//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());
}
@ -223,8 +196,7 @@ class Notes extends Extension {
$user = User::by_name($matches[1]);
if(!is_null($user)) {
$user_id = $user->id;
}
else {
} else {
$user_id = -1;
}
@ -250,8 +222,8 @@ class Notes extends Extension {
"SELECT * ".
"FROM notes ".
"WHERE enable = ? AND image_id = ? ".
"ORDER BY date ASC"
, array('1', $imageID));
"ORDER BY date ASC",
array('1', $imageID));
}
@ -270,23 +242,21 @@ class Notes extends Extension {
$noteText = html_escape($_POST["note_text"]);
$database->execute("
INSERT INTO notes
(enable, image_id, user_id, user_ip, date, x1, y1, height, width, note)
VALUES
(?, ?, ?, ?, now(), ?, ?, ?, ?, ?)",
INSERT INTO notes (enable, image_id, user_id, user_ip, date, x1, y1, height, width, note)
VALUES (?, ?, ?, ?, now(), ?, ?, ?, ?, ?)",
array(1, $imageID, $user_id, $_SERVER['REMOTE_ADDR'], $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText));
$noteID = $database->get_last_insert_id('notes_id_seq');
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);
}
/*
* HERE WE ADD A REQUEST TO DATABASE
*/
@ -297,80 +267,68 @@ class Notes extends Extension {
$user_id = $user->id;
$database->execute("
INSERT INTO note_request
(image_id, user_id, date)
VALUES
(?, ?, now())",
INSERT INTO note_request (image_id, user_id, date)
VALUES (?, ?, now())",
array($image_id, $user_id));
$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}");
}
/*
* HERE WE EDIT THE NOTE
*/
private function update_note()
{
$imageID = int_escape($_POST["image_id"]);
$noteID = int_escape($_POST["note_id"]);
$noteX1 = int_escape($_POST["note_x1"]);
$noteY1 = int_escape($_POST["note_y1"]);
$noteHeight = int_escape($_POST["note_height"]);
$noteWidth = int_escape($_POST["note_width"]);
$noteText = sql_escape(html_escape($_POST["note_text"]));
private function update_note() {
global $database;
$note = array(
"noteX1" => int_escape($_POST["note_x1"]),
"noteY1" => int_escape($_POST["note_y1"]),
"noteHeight" => int_escape($_POST["note_height"]),
"noteWidth" => int_escape($_POST["note_width"]),
"noteText" => sql_escape(html_escape($_POST["note_text"])),
"imageID" => int_escape($_POST["image_id"]),
"noteID" => int_escape($_POST["note_id"])
);
// validate parameters
if (is_null($imageID) || !is_numeric($imageID) ||
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)
{
if (array_search(NULL, $note)|| strlen($note['noteText']) == 0) {
return;
}
global $database;
$database->execute("UPDATE notes ".
"SET x1 = ?, ".
"y1 = ?, ".
"height = ?, ".
"width = ?,".
"note = ? ".
"WHERE image_id = ? AND id = ?", array($noteX1, $noteY1, $noteHeight, $noteWidth, $noteText, $imageID, $noteID));
$this->add_history(1, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText);
"SET x1 = ?, ".
"y1 = ?, ".
"height = ?, ".
"width = ?,".
"note = ? ".
"WHERE image_id = ? AND id = ?", array_values($note));
$this->add_history(1, $note['noteID'], $note['imageID'], $note['noteX1'], $note['noteY1'], $note['noteHeight'], $note['noteWidth'], $note['noteText']);
}
/*
* HERE WE DELETE THE NOTE
*/
private function delete_note()
{
global $user;
private function delete_note() {
global $user, $database;
$imageID = int_escape($_POST["image_id"]);
$noteID = int_escape($_POST["note_id"]);
// validate parameters
if( is_null($imageID) || !is_numeric($imageID) ||
is_null($noteID) || !is_numeric($noteID))
{
if(is_null($imageID) || !is_numeric($imageID) || is_null($noteID) || !is_numeric($noteID)) {
return;
}
global $database;
$database->execute("UPDATE notes ".
"SET enable = ? ".
"WHERE image_id = ? AND id = ?", array(0, $imageID, $noteID));
"SET enable = ? ".
"WHERE image_id = ? AND id = ?", array(0, $imageID, $noteID));
log_info("notes", "Note deleted {$noteID} by {$user->name}");
}
@ -385,9 +343,9 @@ class Notes extends Extension {
$database->execute("DELETE FROM notes WHERE image_id = ?", array($image_id));
log_info("notes", "Notes deleted from {$image_id} by {$user->name}");
}
/*
* HERE WE DELETE ALL REQUESTS FOR IMAGE
*/
@ -408,35 +366,28 @@ class Notes extends Extension {
global $database, $config;
$pageNumber = $event->get_arg(1);
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else
$pageNumber--;
if(is_null($pageNumber) || !is_numeric($pageNumber) || $pageNumber <= 0) {
$pageNumber = 0;
} else {
$pageNumber--;
}
$notesPerPage = $config->get_int('notesNotesPerPage');
//$result = $database->get_all("SELECT * FROM pool_images WHERE pool_id=?", array($poolID));
$get_notes = "
SELECT DISTINCT image_id ".
"FROM notes ".
"WHERE enable = ? ".
"ORDER BY date DESC LIMIT ?, ?";
$result = $database->Execute($get_notes, array(1, $pageNumber * $notesPerPage, $notesPerPage));
//$result = $database->get_all("SELECT * FROM pool_images WHERE pool_id=?", array($poolID));
$result = $database->execute("SELECT DISTINCT image_id".
"FROM notes ".
"WHERE enable = ? ".
"ORDER BY date DESC LIMIT ?, ?",
array(1, $pageNumber * $notesPerPage, $notesPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(DISTINCT image_id) FROM notes") / $notesPerPage);
$images = array();
while(!$result->EOF) {
$image = Image::by_id($result->fields["image_id"]);
$images[] = array($image);
$result->MoveNext();
while($row = $result->fetch()) {
$images[] = array(Image::by_id($row["image_id"]));
}
$this->theme->display_note_list($images, $pageNumber + 1, $totalPages);
}
@ -445,60 +396,52 @@ class Notes extends Extension {
* @param PageRequestEvent $event
*/
private function get_notes_requests(PageRequestEvent $event) {
global $config;
global $config, $database;
$pageNumber = $event->get_arg(1);
if(is_null($pageNumber) || !is_numeric($pageNumber))
if(is_null($pageNumber) || !is_numeric($pageNumber) || $pageNumber <= 0) {
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else
} else {
$pageNumber--;
}
$requestsPerPage = $config->get_int('notesRequestsPerPage');
//$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);
$images = array();
while(!$result->EOF) {
$image = Image::by_id($result->fields["image_id"]);
$images[] = array($image);
$result->MoveNext();
while($row = $result->fetch()) {
$images[] = array(Image::by_id($row["image_id"]));
}
$this->theme->display_note_requests($images, $pageNumber + 1, $totalPages);
}
/*
* HERE WE ADD HISTORY TO TRACK THE CHANGES OF THE NOTES FOR THE IMAGES.
*/
private function add_history($noteEnable, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText){
global $user, $database;
$userID = $user->id;
$reviewID = $database->get_one("SELECT COUNT(*) FROM note_histories WHERE note_id = ?", array($noteID));
$reviewID = $reviewID + 1;
$database->execute("
INSERT INTO note_histories
(note_enable, note_id, review_id, image_id, user_id, user_ip, date, x1, y1, height, width, note)
VALUES
(?, ?, ?, ?, ?, ?, now(), ?, ?, ?, ?, ?)",
array($noteEnable, $noteID, $reviewID, $imageID, $userID, $_SERVER['REMOTE_ADDR'], $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText));
INSERT INTO note_histories (note_enable, note_id, review_id, image_id, user_id, user_ip, date, x1, y1, height, width, note)
VALUES (?, ?, ?, ?, ?, ?, now(), ?, ?, ?, ?, ?)",
array($noteEnable, $noteID, $reviewID, $imageID, $user->id, $_SERVER['REMOTE_ADDR'], $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText));
}
@ -507,29 +450,27 @@ class Notes extends Extension {
* @param PageRequestEvent $event
*/
private function get_histories(PageRequestEvent $event){
$pageNumber = $event->get_arg(1);
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else
$pageNumber--;
global $config, $database;
$pageNumber = $event->get_arg(1);
if (is_null($pageNumber) || !is_numeric($pageNumber) || $pageNumber <= 0) {
$pageNumber = 0;
} else {
$pageNumber--;
}
$historiesPerPage = $config->get_int('notesHistoriesPerPage');
global $config;
$histiriesPerPage = $config->get_int('notesHistoriesPerPage');
//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 ".
"FROM note_histories AS h ".
"INNER JOIN users AS u ".
"ON u.id = h.user_id ".
"ORDER BY date DESC LIMIT ?, ?",
array($pageNumber * $histiriesPerPage, $histiriesPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_histories") / $histiriesPerPage);
"FROM note_histories AS h ".
"INNER JOIN users AS u ".
"ON u.id = h.user_id ".
"ORDER BY date DESC LIMIT ?, ?",
array($pageNumber * $historiesPerPage, $historiesPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_histories") / $historiesPerPage);
$this->theme->display_histories($histories, $pageNumber + 1, $totalPages);
}
@ -539,30 +480,29 @@ class Notes extends Extension {
* @param PageRequestEvent $event
*/
private function get_history(PageRequestEvent $event){
$noteID = $event->get_arg(1);
$pageNumber = $event->get_arg(2);
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else
$pageNumber--;
global $config, $database;
$noteID = $event->get_arg(1);
$pageNumber = $event->get_arg(2);
if (is_null($pageNumber) || !is_numeric($pageNumber) || $pageNumber <= 0) {
$pageNumber = 0;
} else {
$pageNumber--;
}
$historiesPerPage = $config->get_int('notesHistoriesPerPage');
global $config;
$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 ".
"FROM note_histories AS h ".
"INNER JOIN users AS u ".
"ON u.id = h.user_id ".
"WHERE note_id = ? ".
"ORDER BY date DESC LIMIT ?, ?",
array($noteID, $pageNumber * $histiriesPerPage, $histiriesPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_histories WHERE note_id = ?", array($noteID)) / $histiriesPerPage);
"FROM note_histories AS h ".
"INNER JOIN users AS u ".
"ON u.id = h.user_id ".
"WHERE note_id = ? ".
"ORDER BY date DESC LIMIT ?, ?",
array($noteID, $pageNumber * $historiesPerPage, $historiesPerPage));
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM note_histories WHERE note_id = ?", array($noteID)) / $historiesPerPage);
$this->theme->display_history($histories, $pageNumber + 1, $totalPages);
}
@ -573,28 +513,23 @@ class Notes extends Extension {
*/
private function revert_history($noteID, $reviewID){
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'];
$noteID = $history['note_id'];
$imageID = $history['image_id'];
$noteX1 = $history['x1'];
$noteY1 = $history['y1'];
$noteID = $history['note_id'];
$imageID = $history['image_id'];
$noteX1 = $history['x1'];
$noteY1 = $history['y1'];
$noteHeight = $history['height'];
$noteWidth = $history['width'];
$noteText = $history['note'];
$noteWidth = $history['width'];
$noteText = $history['note'];
$database->execute("UPDATE notes ".
"SET enable = ?, ".
"x1 = ?, ".
"y1 = ?, ".
"height = ?, ".
"width = ?,".
"note = ? ".
"WHERE image_id = ? AND id = ?", array(1, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText, $imageID, $noteID));
"SET enable = ?, x1 = ?, y1 = ?, 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);
}
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<?php
class NotesTheme extends Themelet {
public function note_button($image_id) {
return '
return '
<!-- <a href="#" id="addnotelink" >Add a note</a> -->
<form action="" method="">
<input type="button" id="addnote" value="Add Note">
@ -30,42 +30,45 @@ class NotesTheme extends Themelet {
</form>
';
}
public function search_notes_page(Page $page) { //IN DEVELOPMENT, NOT FULLY WORKING
$html = '<form method="GET" action="'.make_link("post/list/note=").'">
<input placeholder="Search Notes" type="text" name="search"/>
<input type="submit" style="display: none;" value="Find"/>
</form>';
$page->set_title(html_escape("Search Note"));
$page->set_heading(html_escape("Search Note"));
$page->add_block(new Block("Search Note", $html, "main", 10));
}
// 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();
foreach($recovered_notes as $note)
{
foreach($recovered_notes as $note) {
$parsedNote = $note["note"];
$parsedNote = str_replace("\n", "\\n", $parsedNote);
$parsedNote = str_replace("\r", "\\r", $parsedNote);
$to_json[] = array(
'x1' => $note["x1"],
'y1' => $note["y1"],
'height' => $note["height"],
'width' => $note["width"],
'note' => $parsedNote,
'x1' => $note["x1"],
'y1' => $note["y1"],
'height' => $note["height"],
'width' => $note["width"],
'note' => $parsedNote,
'note_id' => $note["id"],
);
}
$html = "<script type='text/javascript'>";
$html .= "notes = " . json_encode($to_json);
$html .= "</script>
$html = "<script type='text/javascript'>notes = ".json_encode($to_json)."</script>";
$html .= "
<div id='noteform'>
".make_form(make_link("note/add_note"))."
<input type='hidden' name='image_id' value='".$image_id."' />
@ -73,7 +76,7 @@ class NotesTheme extends Themelet {
<input name='note_y1' type='hidden' value='' id='NoteY1' />
<input name='note_height' type='hidden' value='' id='NoteHeight' />
<input name='note_width' type='hidden' value='' id='NoteWidth' />
<table>
<tr>
<td colspan='2'>
@ -85,7 +88,7 @@ class NotesTheme extends Themelet {
<td><input type='button' value='Cancel' id='cancelnote' /></td>
</tr>
</table>
</form>
</div>
<div id='noteEditForm'>
@ -124,10 +127,10 @@ class NotesTheme extends Themelet {
$html .= "</div>";
$page->add_block(new Block(null, $html, "main", 1));
$page->add_block(new Block(null, $html, "main", 1, 'note_system'));
}
public function display_note_list($images, $pageNumber, $totalPages) {
global $page;
$pool_images = '';
@ -137,20 +140,21 @@ class NotesTheme extends Themelet {
$thumb_html = $this->build_thumb_html($image);
$pool_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
' <a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
}
$this->display_paginator($page, "note/list", null, $pageNumber, $totalPages);
$page->set_title("Notes");
$page->set_heading("Notes");
$page->add_block(new Block("Notes", $pool_images, "main", 20));
}
public function display_note_requests($images, $pageNumber, $totalPages) {
global $page;
$pool_images = '';
foreach($images as $pair) {
$image = $pair[0];
@ -158,13 +162,13 @@ class NotesTheme extends Themelet {
$thumb_html = $this->build_thumb_html($image);
$pool_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
' <a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
}
$this->display_paginator($page, "requests/list", null, $pageNumber, $totalPages);
$page->set_title("Note Requests");
$page->set_heading("Note Requests");
$page->add_block(new Block("Note Requests", $pool_images, "main", 20));
@ -174,19 +178,19 @@ class NotesTheme extends Themelet {
global $user;
$html = "<table id='poolsList' class='zebra'>".
"<thead><tr>".
"<th>Image</th>".
"<th>Note</th>".
"<th>Body</th>".
"<th>Updater</th>".
"<th>Date</th>";
"<thead><tr>".
"<th>Image</th>".
"<th>Note</th>".
"<th>Body</th>".
"<th>Updater</th>".
"<th>Date</th>";
if(!$user->is_anonymous()){
$html .= "<th>Action</th>";
}
$html .= "</tr></thead>".
"<tbody>";
"<tbody>";
foreach($histories as $history) {
$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>";
$html .= "<tr>".
"<td>".$image_link."</td>".
"<td>".$history_link."</td>".
"<td style='text-align:left;'>".$history['note']."</td>".
"<td>".$user_link."</td>".
"<td>".autodate($history['date'])."</td>";
"<td>".$image_link."</td>".
"<td>".$history_link."</td>".
"<td style='text-align:left;'>".$history['note']."</td>".
"<td>".$user_link."</td>".
"<td>".autodate($history['date'])."</td>";
if(!$user->is_anonymous()){
$html .= "<td>".$revert_link."</td>";
@ -223,7 +227,7 @@ class NotesTheme extends Themelet {
$this->display_paginator($page, "note/updated", null, $pageNumber, $totalPages);
}
public function display_history($histories, $pageNumber, $totalPages) {
global $page;
@ -232,8 +236,7 @@ class NotesTheme extends Themelet {
$page->set_title("Note History");
$page->set_heading("Note History");
$page->add_block(new Block("Note History", $html, "main", 10));
$this->display_paginator($page, "note/updated", null, $pageNumber, $totalPages);
}
}

View File

@ -11,7 +11,7 @@
*/
class NumericScoreSetEvent extends Event {
var $image_id, $user, $score;
public $image_id, $user, $score;
/**
* @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)",
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";
$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";
$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) {
$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;
$score = ($matches[1] == "up" ? 1 : ($matches[1] == "down" ? -1 : 0));
if(!$user->is_anonymous()) {

View File

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

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