diff --git a/.php_cs.dist b/.php_cs.dist index 9430b4e5..c36c9cd2 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -3,6 +3,7 @@ $finder = PhpCsFixer\Finder::create() ->exclude('ext/amazon_s3/lib') ->exclude('vendor') + ->exclude('data') ->in(__DIR__) ; diff --git a/README.markdown b/README.markdown index bd71aa06..853a37ee 100644 --- a/README.markdown +++ b/README.markdown @@ -99,24 +99,24 @@ For example, one can override the default anonymous "allow nothing" permissions like so: ```php -new UserClass("anonymous", "base", array( - "create_comment" => True, - "edit_image_tag" => True, - "edit_image_source" => True, - "create_image_report" => True, -)); +new UserClass("anonymous", "base", [ + Permissions::CREATE_COMMENT => True, + Permissions::EDIT_IMAGE_TAG => True, + Permissions::EDIT_IMAGE_SOURCE => True, + Permissions::CREATE_IMAGE_REPORT => True, +]); ``` For a moderator class, being a regular user who can delete images and comments: ```php -new UserClass("moderator", "user", array( - "delete_image" => True, - "delete_comment" => True, -)); +new UserClass("moderator", "user", [ + Permissions::DELETE_IMAGE => True, + Permissions::DELETE_COMMENT => True, +]); ``` -For a list of permissions, see `core/userclass.php` +For a list of permissions, see `core/permissions.php` # Development Info diff --git a/composer.json b/composer.json index 114d990e..746582e0 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "ifixit/php-akismet" : "1.*", "google/recaptcha" : "~1.1", "dapphp/securimage" : "3.6.*", - "shish/libcontext-php" : "dev-master", + "shish/eventtracer-php" : "dev-master", "enshrined/svg-sanitize" : "0.8.2", "bower-asset/jquery" : "1.12.3", @@ -42,7 +42,7 @@ }, "require-dev" : { - "phpunit/phpunit" : "6.*" + "phpunit/phpunit" : "7.*" }, "suggest": { diff --git a/composer.lock b/composer.lock index 2805f8e2..210c3c56 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fd0ccce172ded2999f5ced0884990541", + "content-hash": "b1d9675735299d17a988bc9dfc8e5973", "packages": [ { "name": "bower-asset/jquery", @@ -17,8 +17,7 @@ "dist": { "type": "zip", "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/3a43d7e563314bf32970b773dd31ecf2b90813dd", - "reference": "3a43d7e563314bf32970b773dd31ecf2b90813dd", - "shasum": null + "reference": "3a43d7e563314bf32970b773dd31ecf2b90813dd" }, "type": "bower-asset", "license": [ @@ -36,8 +35,7 @@ "dist": { "type": "zip", "url": "https://api.github.com/repos/rmm5t/jquery-timeago/zipball/67c11951ae9b6020341c1056a42b5406162db40c", - "reference": "67c11951ae9b6020341c1056a42b5406162db40c", - "shasum": null + "reference": "67c11951ae9b6020341c1056a42b5406162db40c" }, "require": { "bower-asset/jquery": ">=1.4" @@ -58,8 +56,7 @@ "dist": { "type": "zip", "url": "https://api.github.com/repos/js-cookie/js-cookie/zipball/5c830fb71a2bd3acce9cb733d692e13316991891", - "reference": "5c830fb71a2bd3acce9cb733d692e13316991891", - "shasum": null + "reference": "5c830fb71a2bd3acce9cb733d692e13316991891" }, "type": "bower-asset", "license": [ @@ -77,8 +74,7 @@ "dist": { "type": "zip", "url": "https://api.github.com/repos/mediaelement/mediaelement/zipball/6e80b260172f4ddc3b0bbee046775d2ba4c6f9b7", - "reference": "6e80b260172f4ddc3b0bbee046775d2ba4c6f9b7", - "shasum": null + "reference": "6e80b260172f4ddc3b0bbee046775d2ba4c6f9b7" }, "type": "bower-asset", "license": [ @@ -96,8 +92,7 @@ "dist": { "type": "zip", "url": "https://api.github.com/repos/christianbach/tablesorter/zipball/07e0918254df3c2057d6d8e4653a0769f1881412", - "reference": "07e0918254df3c2057d6d8e4653a0769f1881412", - "shasum": null + "reference": "07e0918254df3c2057d6d8e4653a0769f1881412" }, "type": "bower-asset", "license": [ @@ -107,21 +102,21 @@ }, { "name": "dapphp/securimage", - "version": "3.6.6", + "version": "3.6.7", "source": { "type": "git", "url": "https://github.com/dapphp/securimage.git", - "reference": "6eea2798f56540fa88356c98f282d6391a72be15" + "reference": "1ecb884797c66e01a875c058def46c85aecea45b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dapphp/securimage/zipball/6eea2798f56540fa88356c98f282d6391a72be15", - "reference": "6eea2798f56540fa88356c98f282d6391a72be15", + "url": "https://api.github.com/repos/dapphp/securimage/zipball/1ecb884797c66e01a875c058def46c85aecea45b", + "reference": "1ecb884797c66e01a875c058def46c85aecea45b", "shasum": "" }, "require": { "ext-gd": "*", - "php": ">=5.2.0" + "php": ">=5.4" }, "suggest": { "ext-pdo": "For database storage support", @@ -136,7 +131,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD" + "BSD-3-Clause" ], "authors": [ { @@ -147,10 +142,12 @@ "description": "PHP CAPTCHA Library", "homepage": "https://www.phpcaptcha.org", "keywords": [ + "Forms", + "anti-spam", "captcha", "security" ], - "time": "2017-11-21T02:29:19+00:00" + "time": "2018-03-09T06:07:41+00:00" }, { "name": "enshrined/svg-sanitize", @@ -248,24 +245,26 @@ "source": { "type": "git", "url": "https://github.com/google/recaptcha.git", - "reference": "6990961e664372ddbed7ebc1cd673da7077552e5" + "reference": "b1b674a50962a73e12125ad746f471c916478978" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/google/recaptcha/zipball/6990961e664372ddbed7ebc1cd673da7077552e5", - "reference": "6990961e664372ddbed7ebc1cd673da7077552e5", + "url": "https://api.github.com/repos/google/recaptcha/zipball/b1b674a50962a73e12125ad746f471c916478978", + "reference": "b1b674a50962a73e12125ad746f471c916478978", "shasum": "" }, "require": { "php": ">=5.5" }, "require-dev": { - "phpunit/phpunit": "^4.8" + "friendsofphp/php-cs-fixer": "^2.2.20|^2.15", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^4.8.36|^5.7.27|^6.59|^7.5.11" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -277,15 +276,15 @@ "license": [ "BSD-3-Clause" ], - "description": "Client library for reCAPTCHA, a free service that protect websites from spam and abuse.", - "homepage": "http://www.google.com/recaptcha/", + "description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.", + "homepage": "https://www.google.com/recaptcha/", "keywords": [ "Abuse", "captcha", "recaptcha", "spam" ], - "time": "2017-03-09T18:57:45+00:00" + "time": "2019-05-24T12:37:13+00:00" }, { "name": "ifixit/php-akismet", @@ -298,26 +297,33 @@ "type": "library" }, { - "name": "shish/libcontext-php", + "name": "shish/eventtracer-php", "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/shish/libcontext-php.git", - "reference": "f57c377e0a5e700fb4d9406e47051a3b7478170e" + "url": "https://github.com/shish/eventtracer-php.git", + "reference": "f49a597b9b048421fcd5077be68adbdfbdd133fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shish/libcontext-php/zipball/f57c377e0a5e700fb4d9406e47051a3b7478170e", - "reference": "f57c377e0a5e700fb4d9406e47051a3b7478170e", + "url": "https://api.github.com/repos/shish/eventtracer-php/zipball/f49a597b9b048421fcd5077be68adbdfbdd133fe", + "reference": "f49a597b9b048421fcd5077be68adbdfbdd133fe", "shasum": "" }, "require": { - "php": ">=7.0" + "ext-json": "*", + "ext-posix": "*", + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "6.*" + "phpunit/phpunit": "^7" }, "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -330,44 +336,42 @@ "role": "Developer" } ], - "description": "A performance monitoring thing", - "homepage": "https://github.com/shish/libcontext-php", - "keywords": [ - "performance", - "profiler" - ], - "time": "2017-09-21T13:25:55+00:00" + "description": "An API to write JSON traces as used by the Chrome Trace Viewer", + "homepage": "https://github.com/shish/eventtracer-php", + "time": "2019-07-07T12:22:39+00:00" } ], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.0.x-dev", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "7c71fc2932158d00f24f10635bf3b3b8b6ee5b68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/7c71fc2932158d00f24f10635bf3b3b8b6ee5b68", + "reference": "7c71fc2932158d00f24f10635bf3b3b8b6ee5b68", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -387,34 +391,37 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2019-07-02T13:37:32+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.x-dev", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -437,7 +444,7 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "time": "2019-04-07T13:18:21+00:00" }, { "name": "phar-io/manifest", @@ -445,18 +452,18 @@ "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "014feadb268809af7c8e2f7ccd396b8494901f58" + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/014feadb268809af7c8e2f7ccd396b8494901f58", - "reference": "014feadb268809af7c8e2f7ccd396b8494901f58", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^1.0.1", + "phar-io/version": "^2.0", "php": "^5.6 || ^7.0" }, "type": "library", @@ -492,20 +499,20 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-04-07T07:07:10+00:00" + "time": "2018-07-08T19:23:20+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", "shasum": "" }, "require": { @@ -539,7 +546,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "time": "2018-07-08T19:19:57+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -597,16 +604,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", "shasum": "" }, "require": { @@ -644,7 +651,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "time": "2019-04-30T17:48:53+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -699,34 +706,34 @@ "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -754,44 +761,44 @@ "spy", "stub" ], - "time": "2018-02-19T10:16:54+00:00" + "time": "2019-06-13T12:50:23+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.x-dev", + "version": "6.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "982ce790a6f31b8f1319a15d86e4614b109af25e" + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/982ce790a6f31b8f1319a15d86e4614b109af25e", - "reference": "982ce790a6f31b8f1319a15d86e4614b109af25e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.0", - "phpunit/php-file-iterator": "^1.4.2", + "php": "^7.1", + "phpunit/php-file-iterator": "^2.0", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", + "phpunit/php-token-stream": "^3.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", + "sebastian/environment": "^3.1 || ^4.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^7.0" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-xdebug": "^2.6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -817,29 +824,32 @@ "testing", "xunit" ], - "time": "2017-12-07T10:13:30+00:00" + "time": "2018-10-31T16:06:48+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.x-dev", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "7f0f29702170e2786b2df813af970135765de6fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/7f0f29702170e2786b2df813af970135765de6fc", + "reference": "7f0f29702170e2786b2df813af970135765de6fc", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -854,7 +864,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -864,7 +874,7 @@ "filesystem", "iterator" ], - "time": "2017-11-27T13:52:08+00:00" + "time": "2019-07-02T07:44:20+00:00" }, { "name": "phpunit/php-text-template", @@ -909,28 +919,28 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.x-dev", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "9513098641797ce5f459dbc1de5a54c29b0ec1fb" + "reference": "37d2894f3650acccb6e57207e63eb9699c1a82a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/9513098641797ce5f459dbc1de5a54c29b0ec1fb", - "reference": "9513098641797ce5f459dbc1de5a54c29b0ec1fb", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/37d2894f3650acccb6e57207e63eb9699c1a82a6", + "reference": "37d2894f3650acccb6e57207e63eb9699c1a82a6", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -945,7 +955,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -954,33 +964,33 @@ "keywords": [ "timer" ], - "time": "2018-01-06T05:27:16+00:00" + "time": "2019-07-02T07:42:03+00:00" }, { "name": "phpunit/php-token-stream", - "version": "2.0.x-dev", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "13eb9aba9626b1a3811c6a492acc9669d24bb85a" + "reference": "98831941cc60e07e7e94d4ff000440015f60da67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/13eb9aba9626b1a3811c6a492acc9669d24bb85a", - "reference": "13eb9aba9626b1a3811c6a492acc9669d24bb85a", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/98831941cc60e07e7e94d4ff000440015f60da67", + "reference": "98831941cc60e07e7e94d4ff000440015f60da67", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2.4" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1003,57 +1013,57 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27T08:47:38+00:00" + "time": "2019-07-02T07:43:15+00:00" }, { "name": "phpunit/phpunit", - "version": "6.5.x-dev", + "version": "7.5.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "80798b8043cb3b4e770c21e64d4fbc2efdda7942" + "reference": "e2e8fb619beae918f726d7c861ea72d310c5458d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/80798b8043cb3b4e770c21e64d4fbc2efdda7942", - "reference": "80798b8043cb3b4e770c21e64d4fbc2efdda7942", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e2e8fb619beae918f726d7c861ea72d310c5458d", + "reference": "e2e8fb619beae918f726d7c861ea72d310c5458d", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.0", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.1", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", - "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", - "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", - "sebastian/environment": "^3.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.0", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", + "sebastian/resource-operations": "^2.0", "sebastian/version": "^2.0.1" }, "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" + "phpunit/phpunit-mock-objects": "*" }, "require-dev": { "ext-pdo": "*" }, "suggest": { + "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "phpunit/php-invoker": "^2.0" }, "bin": [ "phpunit" @@ -1061,7 +1071,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -1087,66 +1097,7 @@ "testing", "xunit" ], - "time": "2018-02-16T06:05:42+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "5.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "e244c19aec6a1f0a2ff9e498b9b4bed22537730a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/e244c19aec6a1f0a2ff9e498b9b4bed22537730a", - "reference": "e244c19aec6a1f0a2ff9e498b9b4bed22537730a", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.0", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2018-01-07T17:10:51+00:00" + "time": "2019-07-05T14:16:20+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1154,12 +1105,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "3488be0a7b346cd6e5361510ed07e88f9bea2e88" + "reference": "5e860800beea5ea4c8590df866338c09c20d3a48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/3488be0a7b346cd6e5361510ed07e88f9bea2e88", - "reference": "3488be0a7b346cd6e5361510ed07e88f9bea2e88", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e860800beea5ea4c8590df866338c09c20d3a48", + "reference": "5e860800beea5ea4c8590df866338c09c20d3a48", "shasum": "" }, "require": { @@ -1191,7 +1142,7 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T10:23:55+00:00" + "time": "2019-07-02T07:44:03+00:00" }, { "name": "sebastian/comparator", @@ -1199,26 +1150,26 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "reference": "9a1267ac19ecd74163989bcb3e01c5c9587f9e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/9a1267ac19ecd74163989bcb3e01c5c9587f9e3b", + "reference": "9a1267ac19ecd74163989bcb3e01c5c9587f9e3b", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", + "php": "^7.1", + "sebastian/diff": "^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1255,32 +1206,33 @@ "compare", "equality" ], - "time": "2018-02-01T13:46:46+00:00" + "time": "2019-07-02T07:45:15+00:00" }, { "name": "sebastian/diff", - "version": "2.0.x-dev", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "abcc70409ddfb310a8cb41ef0c2e857425438cf4" + "reference": "d7e7810940c78f3343420f76adf92dc437b7a557" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/abcc70409ddfb310a8cb41ef0c2e857425438cf4", - "reference": "abcc70409ddfb310a8cb41ef0c2e857425438cf4", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/d7e7810940c78f3343420f76adf92dc437b7a557", + "reference": "d7e7810940c78f3343420f76adf92dc437b7a557", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1305,9 +1257,12 @@ "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], - "time": "2017-12-14T11:32:19+00:00" + "time": "2019-07-02T07:43:30+00:00" }, { "name": "sebastian/environment", @@ -1315,25 +1270,27 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "eb71ad57e2b937a06c91a60efc647f28187626e9" + "reference": "1c91ab3fb351373cf86ead6006ea9daa8e4ce027" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/eb71ad57e2b937a06c91a60efc647f28187626e9", - "reference": "eb71ad57e2b937a06c91a60efc647f28187626e9", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1c91ab3fb351373cf86ead6006ea9daa8e4ce027", + "reference": "1c91ab3fb351373cf86ead6006ea9daa8e4ce027", "shasum": "" }, "require": { - "ext-posix": "*", - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -1358,7 +1315,7 @@ "environment", "hhvm" ], - "time": "2018-02-09T07:31:46+00:00" + "time": "2019-07-02T07:44:59+00:00" }, { "name": "sebastian/exporter", @@ -1366,12 +1323,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "573f8b71a29cc8afa5f8285d1aee4b4d52717637" + "reference": "97cc7aeb5bbc21a59df4e4e9e976831fa1b41fbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/573f8b71a29cc8afa5f8285d1aee4b4d52717637", - "reference": "573f8b71a29cc8afa5f8285d1aee4b4d52717637", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/97cc7aeb5bbc21a59df4e4e9e976831fa1b41fbe", + "reference": "97cc7aeb5bbc21a59df4e4e9e976831fa1b41fbe", "shasum": "" }, "require": { @@ -1425,20 +1382,20 @@ "export", "exporter" ], - "time": "2017-11-16T09:48:09+00:00" + "time": "2019-07-02T07:44:27+00:00" }, { "name": "sebastian/global-state", - "version": "dev-master", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "a27e666314b2df0ab686c2abdee43ffbda48ac10" + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a27e666314b2df0ab686c2abdee43ffbda48ac10", - "reference": "a27e666314b2df0ab686c2abdee43ffbda48ac10", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", "shasum": "" }, "require": { @@ -1476,7 +1433,7 @@ "keywords": [ "global state" ], - "time": "2017-11-16T09:49:42+00:00" + "time": "2017-04-27T15:39:26+00:00" }, { "name": "sebastian/object-enumerator", @@ -1484,12 +1441,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "a496797f3bd6821bfe2acb594e0901dfb00572dd" + "reference": "63e5a3e0881ebf28c9fbb2a2e12b77d373850c12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/a496797f3bd6821bfe2acb594e0901dfb00572dd", - "reference": "a496797f3bd6821bfe2acb594e0901dfb00572dd", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/63e5a3e0881ebf28c9fbb2a2e12b77d373850c12", + "reference": "63e5a3e0881ebf28c9fbb2a2e12b77d373850c12", "shasum": "" }, "require": { @@ -1523,7 +1480,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-11-16T09:50:04+00:00" + "time": "2019-07-02T07:43:46+00:00" }, { "name": "sebastian/object-reflector", @@ -1531,12 +1488,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "ff755086ff55902772e3fae5dd5f29bcbae68285" + "reference": "3053ae3e6286fdf98769f18ec10894dbc6260a34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/ff755086ff55902772e3fae5dd5f29bcbae68285", - "reference": "ff755086ff55902772e3fae5dd5f29bcbae68285", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3053ae3e6286fdf98769f18ec10894dbc6260a34", + "reference": "3053ae3e6286fdf98769f18ec10894dbc6260a34", "shasum": "" }, "require": { @@ -1568,7 +1525,7 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2018-01-07T16:00:13+00:00" + "time": "2019-07-02T07:44:36+00:00" }, { "name": "sebastian/recursion-context", @@ -1576,12 +1533,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "0f7f5eb7697036c570aff6812a8efe60c417725e" + "reference": "a58220ae18565f6004bbe15321efc4470bfe02fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0f7f5eb7697036c570aff6812a8efe60c417725e", - "reference": "0f7f5eb7697036c570aff6812a8efe60c417725e", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/a58220ae18565f6004bbe15321efc4470bfe02fd", + "reference": "a58220ae18565f6004bbe15321efc4470bfe02fd", "shasum": "" }, "require": { @@ -1621,7 +1578,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-11-16T10:04:08+00:00" + "time": "2019-07-02T07:43:54+00:00" }, { "name": "sebastian/resource-operations", @@ -1629,21 +1586,21 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "fadc83f7c41fb2924e542635fea47ae546816ece" + "reference": "d67fc89d3107c396d161411b620619f3e7a7c270" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/fadc83f7c41fb2924e542635fea47ae546816ece", - "reference": "fadc83f7c41fb2924e542635fea47ae546816ece", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/d67fc89d3107c396d161411b620619f3e7a7c270", + "reference": "d67fc89d3107c396d161411b620619f3e7a7c270", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1663,7 +1620,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2016-10-03T07:43:09+00:00" + "time": "2019-07-02T07:42:50+00:00" }, { "name": "sebastian/version", @@ -1709,17 +1666,75 @@ "time": "2016-10-03T07:35:21+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.1.0", + "name": "symfony/polyfill-ctype", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -1746,24 +1761,25 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-06-13T22:48:21+00:00" }, { "name": "webmozart/assert", - "version": "dev-master", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -1796,19 +1812,21 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2018-12-25T11:19:39+00:00" } ], "aliases": [], "minimum-stability": "dev", "stability-flags": { - "shish/libcontext-php": 20, + "shish/eventtracer-php": 20, "bower-asset/tablesorter": 20 }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.0" + "php": ">=7.1", + "ext-pdo": "*", + "ext-json": "*" }, "platform-dev": [] } diff --git a/core/_bootstrap.php b/core/_bootstrap.php index 2611854e..a003ed6e 100644 --- a/core/_bootstrap.php +++ b/core/_bootstrap.php @@ -4,20 +4,25 @@ * actually do anything as far as the app is concerned */ -global $config, $database, $user, $page, $_shm_ctx; +global $config, $database, $user, $page, $_tracer; require_once "core/sys_config.php"; require_once "core/polyfills.php"; require_once "core/util.php"; -require_once "vendor/shish/libcontext-php/context.php"; require_once "vendor/autoload.php"; // set up and purify the environment _version_check(); _sanitise_environment(); +// The trace system has a certain amount of memory consumption every time it is used, +// so to prevent running out of memory during complex operations code that uses it should +// check if tracer output is enabled before making use of it. +$tracer_enabled = constant('TRACE_FILE')!==null; + // load base files -$_shm_ctx->log_start("Opening files"); +$_tracer->begin("Bootstrap"); +$_tracer->begin("Opening files"); $_shm_files = array_merge( zglob("core/*.php"), zglob("core/{".ENABLED_MODS."}/*.php"), @@ -30,23 +35,27 @@ foreach ($_shm_files as $_shm_filename) { } unset($_shm_files); unset($_shm_filename); -$_shm_ctx->log_endok(); +$_tracer->end(); // connect to the database -$_shm_ctx->log_start("Connecting to DB"); +$_tracer->begin("Connecting to DB"); $database = new Database(); $config = new DatabaseConfig($database); -$_shm_ctx->log_endok(); +$_tracer->end(); // load the theme parts -$_shm_ctx->log_start("Loading themelets"); +$_tracer->begin("Loading themelets"); foreach (_get_themelet_files(get_theme()) as $themelet) { require_once $themelet; } unset($themelet); $page = class_exists("CustomPage") ? new CustomPage() : new Page(); -$_shm_ctx->log_endok(); +$_tracer->end(); // hook up event handlers +$_tracer->begin("Loading extensions"); _load_event_listeners(); +$_tracer->end(); + send_event(new InitExtEvent()); +$_tracer->end(); diff --git a/core/_install.php b/core/_install.php index dcc622be..1b78499e 100644 --- a/core/_install.php +++ b/core/_install.php @@ -20,6 +20,8 @@ ob_start(); date_default_timezone_set('UTC'); +define("DATABASE_TIMEOUT", 10000); + ?> @@ -58,6 +60,9 @@ date_default_timezone_set('UTC'); MySQL' : ""; - $db_p = in_array("pgsql", $drivers) ? '' : ""; - $db_s = in_array("sqlite", $drivers) ? '' : ""; + $db_m = in_array(DatabaseDriver::MYSQL, $drivers) ? '' : ""; + $db_p = in_array(DatabaseDriver::PGSQL, $drivers) ? '' : ""; + $db_s = in_array(DatabaseDriver::SQLITE, $drivers) ? '' : ""; $warn_msg = $warnings ? "

Warnings

".implode("\n

", $warnings) : ""; $err_msg = $errors ? "

Errors

".implode("\n

", $errors) : ""; diff --git a/core/basethemelet.php b/core/basethemelet.php index 897a1f33..b3a2baeb 100644 --- a/core/basethemelet.php +++ b/core/basethemelet.php @@ -58,7 +58,7 @@ class BaseThemelet $tsize = get_thumbnail_size($image->width, $image->height); } else { //Use max thumbnail size if using thumbless filetype - $tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height')); + $tsize = get_thumbnail_size($config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_WIDTH)); } $custom_classes = ""; diff --git a/core/cacheengine.php b/core/cacheengine.php index 61fd84ab..002b10b4 100644 --- a/core/cacheengine.php +++ b/core/cacheengine.php @@ -170,7 +170,7 @@ class Cache { $matches = []; $c = null; - if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches)) { + if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches) && !isset($_GET['DISABLE_CACHE'])) { if ($matches[1] == "memcache") { $c = new MemcacheCache($matches[2]); } elseif ($matches[1] == "memcached") { @@ -188,34 +188,34 @@ class Cache public function get(string $key) { + global $_tracer; + $_tracer->begin("Cache Query", ["key"=>$key]); $val = $this->engine->get($key); - if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { - $hit = $val === false ? "hit" : "miss"; - file_put_contents("data/cache.log", "Cache $hit: $key\n", FILE_APPEND); - } if ($val !== false) { + $res = "hit"; $this->hits++; - return $val; } else { + $res = "miss"; $this->misses++; - return false; } + $_tracer->end(null, ["result"=>$res]); + return $val; } public function set(string $key, $val, int $time=0) { + global $_tracer; + $_tracer->begin("Cache Set", ["key"=>$key, "time"=>$time]); $this->engine->set($key, $val, $time); - if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { - file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND); - } + $_tracer->end(); } public function delete(string $key) { + global $_tracer; + $_tracer->begin("Cache Delete", ["key"=>$key]); $this->engine->delete($key); - if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { - file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND); - } + $_tracer->end(); } public function get_hits(): int diff --git a/core/config.php b/core/config.php index 695fa8f1..c9fb225b 100644 --- a/core/config.php +++ b/core/config.php @@ -144,6 +144,13 @@ abstract class BaseConfig implements Config } } + public function set_default_float(string $name, float $value): void + { + if (is_null($this->get($name))) { + $this->values[$name] = $value; + } + } + public function set_default_string(string $name, string $value): void { if (is_null($this->get($name))) { @@ -170,6 +177,11 @@ abstract class BaseConfig implements Config return (int)($this->get($name, $default)); } + public function get_float(string $name, ?float $default=null): ?float + { + return (float)($this->get($name, $default)); + } + public function get_string(string $name, ?string $default=null): ?string { return $this->get($name, $default); diff --git a/core/database.php b/core/database.php index c6135878..2cbf077f 100644 --- a/core/database.php +++ b/core/database.php @@ -1,9 +1,17 @@ get_driver_name() == "sqlite") { + if (version_compare(PHP_VERSION, "6.9.9") == 1 && $this->get_driver_name() == DatabaseDriver::SQLITE) { $ka = false; } @@ -96,11 +104,11 @@ class Database throw new SCoreException("Can't figure out database engine"); } - if ($db_proto === "mysql") { + if ($db_proto === DatabaseDriver::MYSQL) { $this->engine = new MySQL(); - } elseif ($db_proto === "pgsql") { + } elseif ($db_proto === DatabaseDriver::PGSQL) { $this->engine = new PostgreSQL(); - } elseif ($db_proto === "sqlite") { + } elseif ($db_proto === DatabaseDriver::SQLITE) { $this->engine = new SQLite(); } else { die('Unknown PDO driver: '.$db_proto); @@ -159,6 +167,19 @@ class Database return $this->engine->scoreql_to_sql($input); } + public function scoresql_value_prepare($input) + { + if (is_null($this->engine)) { + $this->connect_engine(); + } + if($input===true) { + return $this->engine->BOOL_Y; + } else if ($input===false) { + return $this->engine->BOOL_N; + } + return $input; + } + public function get_driver_name(): string { if (is_null($this->engine)) { @@ -167,35 +188,16 @@ class Database return $this->engine->name; } - private function count_execs(string $sql, array $inputarray): void + private function count_time(string $method, float $start, string $query, ?array $args): void { - 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); + global $_tracer, $tracer_enabled; + $dur = microtime(true) - $start; + if($tracer_enabled) { + $query = trim(preg_replace('/^[\t ]+/m', '', $query)); // trim leading whitespace + $_tracer->complete($start * 1000000, $dur * 1000000, "DB Query", ["query"=>$query, "args"=>$args, "method"=>$method]); } - if (!is_array($inputarray)) { - $this->query_count++; - } - # handle 2-dimensional input arrays - elseif (is_array(reset($inputarray))) { - $this->query_count += sizeof($inputarray); - } else { - $this->query_count++; - } - } - - private function count_time(string $method, float $start): void - { - 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; + $this->query_count++; + $this->dbtime += $dur; } public function execute(string $query, array $args=[]): PDOStatement @@ -204,7 +206,6 @@ class Database if (is_null($this->db)) { $this->connect_db(); } - $this->count_execs($query, $args); $stmt = $this->db->prepare( "-- " . str_replace("%2F", "/", urlencode(@$_GET['q'])). "\n" . $query @@ -225,7 +226,7 @@ class Database return $stmt; } catch (PDOException $pdoe) { throw new SCoreException($pdoe->getMessage()."

Query: ".$query); - } + } } /** @@ -235,7 +236,18 @@ class Database { $_start = microtime(true); $data = $this->execute($query, $args)->fetchAll(); - $this->count_time("get_all", $_start); + $this->count_time("get_all", $_start, $query, $args); + return $data; + } + + /** + * Execute an SQL query and return a iterable object for use with generators. + */ + public function get_all_iterable(string $query, array $args=[]): PDOStatement + { + $_start = microtime(true); + $data = $this->execute($query, $args); + $this->count_time("get_all_iterable", $_start, $query, $args); return $data; } @@ -246,7 +258,7 @@ class Database { $_start = microtime(true); $row = $this->execute($query, $args)->fetch(); - $this->count_time("get_row", $_start); + $this->count_time("get_row", $_start, $query, $args); return $row ? $row : null; } @@ -256,27 +268,32 @@ class Database public function get_col(string $query, array $args=[]): array { $_start = microtime(true); - $stmt = $this->execute($query, $args); - $res = []; - foreach ($stmt as $row) { - $res[] = $row[0]; - } - $this->count_time("get_col", $_start); + $res = $this->execute($query, $args)->fetchAll(PDO::FETCH_COLUMN); + $this->count_time("get_col", $_start, $query, $args); return $res; } /** - * Execute an SQL query and return the the first row => the second row. + * Execute an SQL query and return the first column of each row as a single iterable object. + */ + public function get_col_iterable(string $query, array $args=[]): Generator + { + $_start = microtime(true); + $stmt = $this->execute($query, $args); + $this->count_time("get_col_iterable", $_start, $query, $args); + foreach ($stmt as $row) { + yield $row[0]; + } + } + + /** + * Execute an SQL query and return the the first column => the second column. */ public function get_pairs(string $query, array $args=[]): array { $_start = microtime(true); - $stmt = $this->execute($query, $args); - $res = []; - foreach ($stmt as $row) { - $res[$row[0]] = $row[1]; - } - $this->count_time("get_pairs", $_start); + $res = $this->execute($query, $args)->fetchAll(PDO::FETCH_KEY_PAIR); + $this->count_time("get_pairs", $_start, $query, $args); return $res; } @@ -287,7 +304,7 @@ class Database { $_start = microtime(true); $row = $this->execute($query, $args)->fetch(); - $this->count_time("get_one", $_start); + $this->count_time("get_one", $_start, $query, $args); return $row[0]; } @@ -296,7 +313,7 @@ class Database */ public function get_last_insert_id(string $seq): int { - if ($this->engine->name == "pgsql") { + if ($this->engine->name == DatabaseDriver::PGSQL) { return $this->db->lastInsertId($seq); } else { return $this->db->lastInsertId(); @@ -326,15 +343,15 @@ class Database $this->connect_db(); } - if ($this->engine->name === "mysql") { + if ($this->engine->name === DatabaseDriver::MYSQL) { return count( $this->get_all("SHOW TABLES") ); - } elseif ($this->engine->name === "pgsql") { + } elseif ($this->engine->name === DatabaseDriver::PGSQL) { return count( $this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'") ); - } elseif ($this->engine->name === "sqlite") { + } elseif ($this->engine->name === DatabaseDriver::SQLITE) { return count( $this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'") ); diff --git a/core/dbengine.php b/core/dbengine.php index bb7c674b..5286a8ad 100644 --- a/core/dbengine.php +++ b/core/dbengine.php @@ -1,9 +1,24 @@ BOOL_Y'", $data); + $data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data); + $data = str_replace(SCORE::BOOL, "ENUM('Y', 'N')", $data); + $data = str_replace(SCORE::DATETIME, "DATETIME", $data); + $data = str_replace(SCORE::NOW, "\"1970-01-01\"", $data); + $data = str_replace(SCORE::STRNORM, "", $data); + $data = str_replace(SCORE::ILIKE, "LIKE", $data); return $data; } @@ -53,8 +71,13 @@ class MySQL extends DBEngine class PostgreSQL extends DBEngine { + + /** @var string */ - public $name = "pgsql"; + public $name = DatabaseDriver::PGSQL; + + public $BOOL_Y = 't'; + public $BOOL_N = 'f'; public function init(PDO $db) { @@ -63,20 +86,20 @@ class PostgreSQL extends DBEngine } else { $db->exec("SET application_name TO 'shimmie [local]';"); } - $db->exec("SET statement_timeout TO 10000;"); + $db->exec("SET statement_timeout TO ".DATABASE_TIMEOUT.";"); } public function scoreql_to_sql(string $data): string { - $data = str_replace("SCORE_AIPK", "SERIAL PRIMARY KEY", $data); - $data = str_replace("SCORE_INET", "INET", $data); - $data = str_replace("SCORE_BOOL_Y", "'t'", $data); - $data = str_replace("SCORE_BOOL_N", "'f'", $data); - $data = str_replace("SCORE_BOOL", "BOOL", $data); - $data = str_replace("SCORE_DATETIME", "TIMESTAMP", $data); - $data = str_replace("SCORE_NOW", "current_timestamp", $data); - $data = str_replace("SCORE_STRNORM", "lower", $data); - $data = str_replace("SCORE_ILIKE", "ILIKE", $data); + $data = str_replace(SCORE::AIPK, "SERIAL PRIMARY KEY", $data); + $data = str_replace(SCORE::INET, "INET", $data); + $data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data); + $data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data); + $data = str_replace(SCORE::BOOL, "BOOL", $data); + $data = str_replace(SCORE::DATETIME, "TIMESTAMP", $data); + $data = str_replace(SCORE::NOW, "current_timestamp", $data); + $data = str_replace(SCORE::STRNORM, "lower", $data); + $data = str_replace(SCORE::ILIKE, "ILIKE", $data); return $data; } @@ -136,7 +159,11 @@ function _ln($n) class SQLite extends DBEngine { /** @var string */ - public $name = "sqlite"; + public $name = DatabaseDriver::SQLITE; + + public $BOOL_Y = 'Y'; + public $BOOL_N = 'N'; + public function init(PDO $db) { @@ -156,14 +183,14 @@ class SQLite extends DBEngine public function scoreql_to_sql(string $data): string { - $data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY", $data); - $data = str_replace("SCORE_INET", "VARCHAR(45)", $data); - $data = str_replace("SCORE_BOOL_Y", "'Y'", $data); - $data = str_replace("SCORE_BOOL_N", "'N'", $data); - $data = str_replace("SCORE_BOOL", "CHAR(1)", $data); - $data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data); - $data = str_replace("SCORE_STRNORM", "lower", $data); - $data = str_replace("SCORE_ILIKE", "LIKE", $data); + $data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY", $data); + $data = str_replace(SCORE::INET, "VARCHAR(45)", $data); + $data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data); + $data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data); + $data = str_replace(SCORE::BOOL, "CHAR(1)", $data); + $data = str_replace(SCORE::NOW, "\"1970-01-01\"", $data); + $data = str_replace(SCORE::STRNORM, "lower", $data); + $data = str_replace(SCORE::ILIKE, "LIKE", $data); return $data; } diff --git a/core/event.php b/core/event.php index 292da5bf..c9064c3e 100644 --- a/core/event.php +++ b/core/event.php @@ -6,6 +6,8 @@ */ abstract class Event { + public $stop_processing = false; + public function __construct() { } @@ -58,7 +60,7 @@ class PageRequestEvent extends Event // if path is not specified, use the default front page if (empty($path)) { /* empty is faster than strlen */ - $path = $config->get_string('front_page'); + $path = $config->get_string(SetupConfig::FRONT_PAGE); } // break the path into parts diff --git a/core/extension.php b/core/extension.php index b7472583..5c8c61fa 100644 --- a/core/extension.php +++ b/core/extension.php @@ -182,7 +182,7 @@ abstract class DataHandlerExtension extends Extension // even more hax.. $event->metadata['tags'] = $existing->get_tag_list(); - $image = $this->create_image_from_data(warehouse_path("images", $event->metadata['hash']), $event->metadata); + $image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->metadata['hash']), $event->metadata); if (is_null($image)) { throw new UploadException("Data handler failed to create image object from data"); @@ -192,13 +192,14 @@ abstract class DataHandlerExtension extends Extension send_event($ire); $event->image_id = $image_id; } else { - $image = $this->create_image_from_data(warehouse_path("images", $event->hash), $event->metadata); + $image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata); if (is_null($image)) { throw new UploadException("Data handler failed to create image object from data"); } $iae = new ImageAdditionEvent($image); send_event($iae); $event->image_id = $iae->image->id; + $event->merged = $iae->merged; // Rating Stuff. if (!empty($event->metadata['rating'])) { @@ -222,13 +223,13 @@ abstract class DataHandlerExtension extends Extension $result = false; if ($this->supported_ext($event->type)) { if ($event->force) { - $result = $this->create_thumb($event->hash); + $result = $this->create_thumb($event->hash, $event->type); } else { - $outname = warehouse_path("thumbs", $event->hash); + $outname = warehouse_path(Image::THUMBNAIL_DIR, $event->hash); if (file_exists($outname)) { return; } - $result = $this->create_thumb($event->hash); + $result = $this->create_thumb($event->hash, $event->type); } } if ($result) { @@ -256,5 +257,5 @@ abstract class DataHandlerExtension extends Extension abstract protected function supported_ext(string $ext): bool; abstract protected function check_contents(string $tmpname): bool; abstract protected function create_image_from_data(string $filename, array $metadata); - abstract protected function create_thumb(string $hash): bool; + abstract protected function create_thumb(string $hash, string $type): bool; } diff --git a/core/imageboard/event.php b/core/imageboard/event.php index 2fc40fef..3064fa03 100644 --- a/core/imageboard/event.php +++ b/core/imageboard/event.php @@ -11,6 +11,8 @@ class ImageAdditionEvent extends Event /** @var Image */ public $image; + public $merged = false; + /** * Inserts a new image into the database with its associated * information. Also calls TagSetEvent to set the tags for @@ -40,15 +42,19 @@ class ImageDeletionEvent extends Event /** @var Image */ public $image; + /** @var bool */ + public $force = false; + /** * Deletes an image. * * Used by things like tags and comments handlers to * clean out related rows in their tables. */ - public function __construct(Image $image) + public function __construct(Image $image, bool $force = false) { $this->image = $image; + $this->force = $force; } } diff --git a/core/imageboard/image.php b/core/imageboard/image.php index 928d4914..598a2ab7 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -10,6 +10,9 @@ */ class Image { + public const IMAGE_DIR = "images"; + public const THUMBNAIL_DIR = "thumbs"; + private static $tag_n = 0; // temp hack public static $order_sql = null; // this feels ugly @@ -51,6 +54,19 @@ class Image /** @var boolean */ public $locked = false; + /** @var boolean */ + public $lossless = null; + + /** @var boolean */ + public $video = null; + + /** @var boolean */ + public $audio = null; + + /** @var int */ + public $length = null; + + /** * One will very rarely construct an image directly, more common * would be to use Image::by_id, Image::by_hash, etc. @@ -100,6 +116,43 @@ class Image } } + + private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags=[]): iterable + { + global $database, $user, $config; + + if ($start < 0) { + $start = 0; + } + if ($limit!=null && $limit < 1) { + $limit = 1; + } + + if (SPEED_HAX) { + if (!$user->can(Permissions::BIG_SEARCH) and count($tags) > 3) { + throw new SCoreException("Anonymous users may only search for up to 3 tags at a time"); + } + } + + list($tag_conditions, $img_conditions) = self::terms_to_conditions($tags); + + $result = Image::get_accelerated_result($tag_conditions, $img_conditions, $start, $limit); + if (!$result) { + $querylet = Image::build_search_querylet($tag_conditions, $img_conditions); + $querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order")))); + if($limit!=null) { + $querylet->append(new Querylet(" LIMIT :limit ", ["limit" => $limit])); + } + $querylet->append(new Querylet(" OFFSET :offset ", ["offset"=>$start])); + #var_dump($querylet->sql); var_dump($querylet->variables); + $result = $database->get_all_iterable($querylet->sql, $querylet->variables); + } + + Image::$order_sql = null; + + return $result; + } + /** * Search for an array of images * @@ -108,92 +161,30 @@ class Image */ public static function find_images(int $start, int $limit, array $tags=[]): array { - global $database, $user, $config; + $result = self::find_images_internal($start, $limit, $tags); $images = []; - - if ($start < 0) { - $start = 0; - } - if ($limit < 1) { - $limit = 1; - } - - if (SPEED_HAX) { - if (!$user->can("big_search") and count($tags) > 3) { - throw new SCoreException("Anonymous users may only search for up to 3 tags at a time"); - } - } - - $result = null; - if (SEARCH_ACCEL) { - $result = Image::get_accelerated_result($tags, $start, $limit); - } - - if (!$result) { - $querylet = Image::build_search_querylet($tags); - $querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order")))); - $querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", ["limit"=>$limit, "offset"=>$start])); - #var_dump($querylet->sql); var_dump($querylet->variables); - $result = $database->execute($querylet->sql, $querylet->variables); - } - - while ($row = $result->fetch()) { + foreach ($result as $row) { $images[] = new Image($row); } - Image::$order_sql = null; return $images; } /** - * Search for an array of image IDs - * - * #param string[] $tags - * #return int[] + * Search for an array of images, returning a iterable object of Image */ - public static function find_image_ids(int $start, int $limit, array $tags=[]): array + public static function find_images_iterable(int $start = 0, ?int $limit = null, array $tags=[]): Generator { - global $database, $user, $config; - - $images = []; - - if ($start < 0) { - $start = 0; + $result = self::find_images_internal($start, $limit, $tags); + foreach ($result as $row) { + yield new Image($row); } - if ($limit < 1) { - $limit = 1; - } - - if (SPEED_HAX) { - if (!$user->can("big_search") and count($tags) > 3) { - throw new SCoreException("Anonymous users may only search for up to 3 tags at a time"); - } - } - - $result = null; - if (SEARCH_ACCEL) { - $result = Image::get_accelerated_result($tags, $start, $limit); - } - - if (!$result) { - $querylet = Image::build_search_querylet($tags); - $querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order")))); - $querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", ["limit"=>$limit, "offset"=>$start])); - #var_dump($querylet->sql); var_dump($querylet->variables); - $result = $database->execute($querylet->sql, $querylet->variables); - } - - while ($row = $result->fetch()) { - $images[] = $row["id"]; - } - Image::$order_sql = null; - return $images; } /* * Accelerator stuff */ - public static function get_acceleratable(array $tags): ?array + public static function get_acceleratable(array $tag_conditions): ?array { $ret = [ "yays" => [], @@ -201,16 +192,17 @@ class Image ]; $yays = 0; $nays = 0; - foreach ($tags as $tag) { - if (!preg_match("/^-?[a-zA-Z0-9_'-]+$/", $tag)) { + foreach ($tag_conditions as $tq) { + if (strpos($tq->tag, "*") !== false) { + // can't deal with wildcards return null; } - if ($tag[0] == "-") { - $nays++; - $ret["nays"][] = substr($tag, 1); - } else { + if ($tq->positive) { $yays++; - $ret["yays"][] = $tag; + $ret["yays"][] = $tq->tag; + } else { + $nays++; + $ret["nays"][] = $tq->tag; } } if ($yays > 1 || $nays > 0) { @@ -219,11 +211,15 @@ class Image return null; } - public static function get_accelerated_result(array $tags, int $offset, int $limit): ?PDOStatement + public static function get_accelerated_result(array $tag_conditions, array $img_conditions, int $offset, ?int $limit): ?PDOStatement { + if (!SEARCH_ACCEL || !empty($img_conditions) || isset($_GET['DISABLE_ACCEL'])) { + return null; + } + global $database; - $req = Image::get_acceleratable($tags); + $req = Image::get_acceleratable($tag_conditions); if (!$req) { return null; } @@ -231,8 +227,8 @@ class Image $req["limit"] = $limit; $response = Image::query_accelerator($req); - $list = implode(",", $response); - if ($list) { + if ($response) { + $list = implode(",", $response); $result = $database->execute("SELECT * FROM images WHERE id IN ($list) ORDER BY images.id DESC"); } else { $result = $database->execute("SELECT * FROM images WHERE 1=0 ORDER BY images.id DESC"); @@ -240,9 +236,13 @@ class Image return $result; } - public static function get_accelerated_count(array $tags): ?int + public static function get_accelerated_count(array $tag_conditions, array $img_conditions): ?int { - $req = Image::get_acceleratable($tags); + if (!SEARCH_ACCEL || !empty($img_conditions) || isset($_GET['DISABLE_ACCEL'])) { + return null; + } + + $req = Image::get_acceleratable($tag_conditions); if (!$req) { return null; } @@ -253,17 +253,21 @@ class Image public static function query_accelerator($req) { + global $_tracer; $fp = @fsockopen("127.0.0.1", 21212); if (!$fp) { return null; } - fwrite($fp, json_encode($req)); + $req_str = json_encode($req); + $_tracer->begin("Accelerator Query", ["req"=>$req_str]); + fwrite($fp, $req_str); $data = ""; while (($buffer = fgets($fp, 4096)) !== false) { $data .= $buffer; } + $_tracer->end(); if (!feof($fp)) { - die("Error: unexpected fgets() fail in query_accelerator($req)\n"); + die("Error: unexpected fgets() fail in query_accelerator($req_str)\n"); } fclose($fp); return json_decode($data); @@ -295,9 +299,10 @@ class Image ["tag"=>$tags[0]] ); } else { - $total = Image::get_accelerated_count($tags); + list($tag_conditions, $img_conditions) = self::terms_to_conditions($tags); + $total = Image::get_accelerated_count($tag_conditions, $img_conditions); if (is_null($total)) { - $querylet = Image::build_search_querylet($tags); + $querylet = Image::build_search_querylet($tag_conditions, $img_conditions); $total = $database->get_one("SELECT COUNT(*) AS cnt FROM ($querylet->sql) AS tbl", $querylet->variables); } } @@ -318,6 +323,49 @@ class Image return ceil(Image::count_images($tags) / $config->get_int('index_images')); } + private static function terms_to_conditions(array $terms): array + { + $tag_conditions = []; + $img_conditions = []; + + /* + * Turn a bunch of strings into a bunch of TagCondition + * and ImgCondition objects + */ + $stpe = new SearchTermParseEvent(null, $terms); + send_event($stpe); + if ($stpe->is_querylet_set()) { + foreach ($stpe->get_querylets() as $querylet) { + $img_conditions[] = new ImgCondition($querylet, true); + } + } + + foreach ($terms as $term) { + $positive = true; + if (is_string($term) && !empty($term) && ($term[0] == '-')) { + $positive = false; + $term = substr($term, 1); + } + if (strlen($term) === 0) { + continue; + } + + $stpe = new SearchTermParseEvent($term, $terms); + send_event($stpe); + if ($stpe->is_querylet_set()) { + foreach ($stpe->get_querylets() as $querylet) { + $img_conditions[] = new ImgCondition($querylet, $positive); + } + } else { + // if the whole match is wild, skip this + if (str_replace("*", "", $term) != "") { + $tag_conditions[] = new TagCondition($term, $positive); + } + } + } + return [$tag_conditions, $img_conditions]; + } + /* * Accessors & mutators */ @@ -352,7 +400,8 @@ class Image '); } else { $tags[] = 'id'. $gtlt . $this->id; - $querylet = Image::build_search_querylet($tags); + list($tag_conditions, $img_conditions) = self::terms_to_conditions($tags); + $querylet = Image::build_search_querylet($tag_conditions, $img_conditions); $querylet->append_sql(' ORDER BY images.id '.$dir.' LIMIT 1'); $row = $database->get_row($querylet->sql, $querylet->variables); } @@ -427,7 +476,7 @@ class Image */ public function get_image_link(): string { - return $this->get_link('image_ilink', '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext'); + return $this->get_link(ImageConfig::ILINK, '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext'); } /** @@ -444,8 +493,8 @@ class Image public function get_thumb_link(): string { global $config; - $ext = $config->get_string("thumb_type"); - return $this->get_link('image_tlink', '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext); + $ext = $config->get_string(ImageConfig::THUMB_TYPE); + return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext); } /** @@ -476,7 +525,7 @@ class Image public function get_tooltip(): string { global $config; - $tt = $this->parse_link_template($config->get_string('image_tip'), "no_escape"); + $tt = $this->parse_link_template($config->get_string(ImageConfig::TIP), "no_escape"); // Removes the size tag if the file is an mp3 if ($this->ext === 'mp3') { @@ -502,7 +551,7 @@ class Image */ public function get_image_filename(): string { - return warehouse_path("images", $this->hash); + return warehouse_path(self::IMAGE_DIR, $this->hash); } /** @@ -510,7 +559,7 @@ class Image */ public function get_thumb_filename(): string { - return warehouse_path("thumbs", $this->hash); + return warehouse_path(self::THUMBNAIL_DIR, $this->hash); } /** @@ -590,7 +639,7 @@ class Image public function delete_tags_from_image(): void { global $database; - if ($database->get_driver_name() == "mysql") { + if ($database->get_driver_name() == DatabaseDriver::MYSQL) { //mysql < 5.6 has terrible subquery optimization, using EXISTS / JOIN fixes this $database->execute( " @@ -764,7 +813,7 @@ class Image $tmpl = str_replace('$size', "{$this->width}x{$this->height}", $tmpl); $tmpl = str_replace('$filesize', to_shorthand_int($this->filesize), $tmpl); $tmpl = str_replace('$filename', $_escape($base_fname), $tmpl); - $tmpl = str_replace('$title', $_escape($config->get_string("title")), $tmpl); + $tmpl = str_replace('$title', $_escape($config->get_string(SetupConfig::TITLE)), $tmpl); $tmpl = str_replace('$date', $_escape(autodate($this->posted, false)), $tmpl); // nothing seems to use this, sending the event out to 50 exts is a lot of overhead @@ -774,16 +823,17 @@ class Image $tmpl = $plte->link; } - static $flexihash = null; - static $fh_last_opts = null; + static $flexihashes = []; $matches = []; if (preg_match("/(.*){(.*)}(.*)/", $tmpl, $matches)) { $pre = $matches[1]; $opts = $matches[2]; $post = $matches[3]; - if ($opts != $fh_last_opts) { - $fh_last_opts = $opts; + if(isset($flexihashes[$opts])) { + $flexihash = $flexihashes[$opts]; + } + else { $flexihash = new Flexihash\Flexihash(); foreach (explode(",", $opts) as $opt) { $parts = explode("=", $opt); @@ -799,6 +849,7 @@ class Image } $flexihash->addTarget($opt_val, $opt_weight); } + $flexihashes[$opts] = $flexihash; } // $choice = $flexihash->lookup($pre.$post); @@ -813,57 +864,17 @@ class Image /** * #param string[] $terms */ - private static function build_search_querylet(array $terms): Querylet + private static function build_search_querylet(array $tag_conditions, array $img_conditions): Querylet { global $database; - $tag_querylets = []; - $img_querylets = []; $positive_tag_count = 0; $negative_tag_count = 0; - - /* - * Turn a bunch of strings into a bunch of TagQuerylet - * and ImgQuerylet objects - */ - $stpe = new SearchTermParseEvent(null, $terms); - send_event($stpe); - if ($stpe->is_querylet_set()) { - foreach ($stpe->get_querylets() as $querylet) { - $img_querylets[] = new ImgQuerylet($querylet, true); - } - } - - foreach ($terms as $term) { - $positive = true; - if (is_string($term) && !empty($term) && ($term[0] == '-')) { - $positive = false; - $term = substr($term, 1); - } - if (strlen($term) === 0) { - continue; - } - - $stpe = new SearchTermParseEvent($term, $terms); - send_event($stpe); - if ($stpe->is_querylet_set()) { - foreach ($stpe->get_querylets() as $querylet) { - $img_querylets[] = new ImgQuerylet($querylet, $positive); - } + foreach ($tag_conditions as $tq) { + if ($tq->positive) { + $positive_tag_count++; } else { - // if the whole match is wild, skip this; - // if not, translate into SQL - if (str_replace("*", "", $term) != "") { - $term = str_replace('_', '\_', $term); - $term = str_replace('%', '\%', $term); - $term = str_replace('*', '%', $term); - $tag_querylets[] = new TagQuerylet($term, $positive); - if ($positive) { - $positive_tag_count++; - } else { - $negative_tag_count++; - } - } + $negative_tag_count++; } } @@ -888,41 +899,20 @@ class Image "); } - // one positive tag (a common case), do an optimised search - elseif ($positive_tag_count === 1 && $negative_tag_count === 0) { - # "LIKE" to account for wildcards - $query = new Querylet($database->scoreql_to_sql(" - SELECT * - FROM ( - SELECT images.* - FROM images - JOIN image_tags ON images.id=image_tags.image_id - JOIN tags ON image_tags.tag_id=tags.id - WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag) - GROUP BY images.id - ) AS images - WHERE 1=1 - "), ["tag"=>$tag_querylets[0]->tag]); - } - // more than one positive tag, or more than zero negative tags else { - if ($database->get_driver_name() === "mysql") { - $query = Image::build_ugly_search_querylet($tag_querylets); - } else { - $query = Image::build_accurate_search_querylet($tag_querylets); - } + $query = Image::build_accurate_search_querylet($tag_conditions); } /* * Merge all the image metadata searches into one generic querylet * and append to the base querylet with "AND blah" */ - if (!empty($img_querylets)) { + if (!empty($img_conditions)) { $n = 0; $img_sql = ""; $img_vars = []; - foreach ($img_querylets as $iq) { + foreach ($img_conditions as $iq) { if ($n++ > 0) { $img_sql .= " AND"; } @@ -940,47 +930,30 @@ class Image } /** - * WARNING: this description is no longer accurate, though it does get across - * the general idea - the actual method has a few extra optimisations - * - * "foo bar -baz user=foo" becomes - * - * SELECT * FROM images WHERE - * images.id IN (SELECT image_id FROM image_tags WHERE tag='foo') - * AND images.id IN (SELECT image_id FROM image_tags WHERE tag='bar') - * AND NOT images.id IN (SELECT image_id FROM image_tags WHERE tag='baz') - * AND images.id IN (SELECT id FROM images WHERE owner_name='foo') - * - * This is: - * A) Incredibly simple: - * Each search term maps to a list of image IDs - * B) Runs really fast on a good database: - * These lists are calculated once, and the set intersection taken - * C) Runs really slow on bad databases: - * All the subqueries are executed every time for every row in the - * images table. Yes, MySQL does suck this much. - * - * #param TagQuerylet[] $tag_querylets + * #param TagQuerylet[] $tag_conditions */ - private static function build_accurate_search_querylet(array $tag_querylets): Querylet + private static function build_accurate_search_querylet(array $tag_conditions): Querylet { global $database; $positive_tag_id_array = []; + $positive_wildcard_id_array = []; $negative_tag_id_array = []; - foreach ($tag_querylets as $tq) { + foreach ($tag_conditions as $tq) { $tag_ids = $database->get_col( $database->scoreql_to_sql(" SELECT id FROM tags WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag) "), - ["tag" => $tq->tag] + ["tag" => Tag::sqlify($tq->tag)] ); + + $tag_count = count($tag_ids); + if ($tq->positive) { - $positive_tag_id_array = array_merge($positive_tag_id_array, $tag_ids); - if (count($tag_ids) == 0) { + if ($tag_count== 0) { # one of the positive tags had zero results, therefor there # can be no results; "where 1=0" should shortcut things return new Querylet(" @@ -988,115 +961,71 @@ class Image FROM images WHERE 1=0 "); + } elseif($tag_count==1) { + // All wildcard terms that qualify for a single tag can be treated the same as non-wildcards + $positive_tag_id_array[] = $tag_ids[0]; + } else { + // Terms that resolve to multiple tags act as an OR within themselves + // and as an AND in relation to all other terms, + $positive_wildcard_id_array[] = $tag_ids; } } else { + // Unlike positive criteria, negative criteria are all handled in an OR fashion, + // so we can just compile them all into a single sub-query. $negative_tag_id_array = array_merge($negative_tag_id_array, $tag_ids); } } - assert($positive_tag_id_array || $negative_tag_id_array, @$_GET['q']); - $wheres = []; - if (!empty($positive_tag_id_array)) { - $positive_tag_id_list = join(', ', $positive_tag_id_array); - $wheres[] = "tag_id IN ($positive_tag_id_list)"; - } - if (!empty($negative_tag_id_array)) { + $sql = ""; + assert($positive_tag_id_array || $positive_wildcard_id_array || $negative_tag_id_array, @$_GET['q']); + if(!empty($positive_tag_id_array) || !empty($positive_wildcard_id_array)) { + $inner_joins = []; + if (!empty($positive_tag_id_array)) { + foreach($positive_tag_id_array as $tag) { + $inner_joins[] = "= $tag"; + } + } + if(!empty($positive_wildcard_id_array)) { + foreach ($positive_wildcard_id_array as $tags) { + $positive_tag_id_list = join(', ', $tags); + $inner_joins[] = "IN ($positive_tag_id_list)"; + } + } + + $first = array_shift($inner_joins); + $sub_query = "SELECT it.image_id FROM image_tags it "; + $i = 0; + foreach ($inner_joins as $inner_join) { + $i++; + $sub_query .= " INNER JOIN image_tags it$i ON it$i.image_id = it.image_id AND it$i.tag_id $inner_join "; + } + if(!empty($negative_tag_id_array)) { + $negative_tag_id_list = join(', ', $negative_tag_id_array); + $sub_query .= " LEFT JOIN image_tags negative ON negative.image_id = it.image_id AND negative.tag_id IN ($negative_tag_id_list) "; + } + $sub_query .= "WHERE it.tag_id $first "; + if(!empty($negative_tag_id_array)) { + $sub_query .= " AND negative.image_id IS NULL"; + } + $sub_query .= " GROUP BY it.image_id "; + + $sql = " + SELECT images.* + FROM images INNER JOIN ( + $sub_query + ) a on a.image_id = images.id + "; + } elseif(!empty($negative_tag_id_array)) { $negative_tag_id_list = join(', ', $negative_tag_id_array); - $wheres[] = "tag_id NOT IN ($negative_tag_id_list)"; - } - $wheres_str = join(" AND ", $wheres); - return new Querylet(" - SELECT images.* - FROM images - WHERE images.id IN ( - SELECT image_id - FROM image_tags - WHERE $wheres_str - GROUP BY image_id - HAVING COUNT(image_id) >= :search_score - ) - ", ["search_score"=>count($positive_tag_id_array)]); - } - - /** - * this function exists because mysql is a turd, see the docs for - * build_accurate_search_querylet() for a full explanation - * - * #param TagQuerylet[] $tag_querylets - */ - private static function build_ugly_search_querylet(array $tag_querylets): Querylet - { - global $database; - - $positive_tag_count = 0; - foreach ($tag_querylets as $tq) { - if ($tq->positive) { - $positive_tag_count++; - } + $sql = " + SELECT images.* + FROM images LEFT JOIN image_tags negative ON negative.image_id = images.id AND negative.tag_id in ($negative_tag_id_list) + WHERE negative.image_id IS NULL + "; + } else { + throw new SCoreException("No criteria specified"); } - // only negative tags - shortcut to fail - if ($positive_tag_count == 0) { - // TODO: This isn't currently implemented. - // SEE: https://github.com/shish/shimmie2/issues/66 - return new Querylet(" - SELECT images.* - FROM images - WHERE 1=0 - "); - } - - // merge all the tag querylets into one generic one - $sql = "0"; - $terms = []; - foreach ($tag_querylets as $tq) { - $sign = $tq->positive ? "+" : "-"; - $sql .= ' '.$sign.' IF(SUM(tag LIKE :tag'.Image::$tag_n.'), 1, 0)'; - $terms['tag'.Image::$tag_n] = $tq->tag; - Image::$tag_n++; - } - $tag_search = new Querylet($sql, $terms); - - $tag_id_array = []; - - foreach ($tag_querylets as $tq) { - $tag_ids = $database->get_col( - $database->scoreql_to_sql(" - SELECT id - FROM tags - WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag) - "), - ["tag" => $tq->tag] - ); - $tag_id_array = array_merge($tag_id_array, $tag_ids); - - if ($tq->positive && count($tag_ids) == 0) { - # one of the positive tags had zero results, therefor there - # can be no results; "where 1=0" should shortcut things - return new Querylet(" - SELECT images.* - FROM images - WHERE 1=0 - "); - } - } - - Image::$tag_n = 0; - return new Querylet(' - SELECT * - FROM ( - SELECT images.*, ('.$tag_search->sql.') AS score - FROM images - LEFT JOIN image_tags ON image_tags.image_id = images.id - JOIN tags ON image_tags.tag_id = tags.id - WHERE tags.id IN (' . join(', ', $tag_id_array) . ') - GROUP BY images.id - HAVING score = :score - ) AS images - WHERE 1=1 - ', array_merge( - $tag_search->variables, - ["score"=>$positive_tag_count] - )); + return new Querylet($sql); } } diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index dbdf25f5..c82d9f98 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -7,11 +7,12 @@ * Move a file from PHP's temporary area into shimmie's image storage * hierarchy, or throw an exception trying. * + * @param DataUploadEvent $event * @throws UploadException */ function move_upload_to_archive(DataUploadEvent $event): void { - $target = warehouse_path("images", $event->hash); + $target = warehouse_path(Image::IMAGE_DIR, $event->hash); if (!@copy($event->tmpname, $target)) { $errors = error_get_last(); throw new UploadException( @@ -24,7 +25,8 @@ function move_upload_to_archive(DataUploadEvent $event): void /** * Add a directory full of images * - * #return string[] + * @param string $base + * @return array */ function add_dir(string $base): array { @@ -48,6 +50,14 @@ function add_dir(string $base): array return $results; } +/** + * Sends a DataUploadEvent for a file. + * + * @param string $tmpname + * @param string $filename + * @param string $tags + * @throws UploadException + */ function add_image(string $tmpname, string $filename, string $tags): void { assert(file_exists($tmpname)); @@ -65,10 +75,15 @@ function add_image(string $tmpname, string $filename, string $tags): void send_event($event); } - -function get_extension_from_mime(String $file_path): ?String +/** + * Gets an the extension defined in MIME_TYPE_MAP for a file. + * + * @param String $file_path + * @return String The extension that was found. + * @throws UploadException if the mimetype could not be determined, or if an extension for hte mimetype could not be found. + */ +function get_extension_from_mime(String $file_path): String { - global $config; $mime = mime_content_type($file_path); if (!empty($mime)) { $ext = get_extension($mime); @@ -80,14 +95,17 @@ function get_extension_from_mime(String $file_path): ?String throw new UploadException("Could not determine file mime type: ".$file_path); } - /** * Given a full size pair of dimensions, return a pair scaled down to fit - * into the configured thumbnail square, with ratio intact + * into the configured thumbnail square, with ratio intact. + * Optionally uses the High-DPI scaling setting to adjust the final resolution. * - * #return int[] + * @param int $orig_width + * @param int $orig_height + * @param bool $use_dpi_scaling Enables the High-DPI scaling. + * @return array */ -function get_thumbnail_size(int $orig_width, int $orig_height): array +function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_scaling = false): array { global $config; @@ -105,363 +123,97 @@ function get_thumbnail_size(int $orig_width, int $orig_height): array $orig_height = $orig_width * 5; } - $max_width = $config->get_int('thumb_width'); - $max_height = $config->get_int('thumb_height'); - $xscale = ($max_height / $orig_height); - $yscale = ($max_width / $orig_width); - $scale = ($xscale < $yscale) ? $xscale : $yscale; + if($use_dpi_scaling) { + list($max_width, $max_height) = get_thumbnail_max_size_scaled(); + } else { + $max_width = $config->get_int(ImageConfig::THUMB_WIDTH); + $max_height = $config->get_int(ImageConfig::THUMB_HEIGHT); + } - if ($scale > 1 && $config->get_bool('thumb_upscale')) { + $output = get_scaled_by_aspect_ratio($orig_width, $orig_height, $max_width, $max_height); + + if ($output[2] > 1 && $config->get_bool('thumb_upscale')) { return [(int)$orig_width, (int)$orig_height]; } else { - return [(int)($orig_width*$scale), (int)($orig_height*$scale)]; + return $output; } + +} + +function get_scaled_by_aspect_ratio(int $original_width, int $original_height, int $max_width, int $max_height) : array +{ + $xscale = ($max_width/ $original_width); + $yscale = ($max_height/ $original_height); + + $scale = ($yscale < $xscale) ? $yscale : $xscale ; + + return [(int)($original_width*$scale), (int)($original_height*$scale), $scale]; } /** - * Given a full size pair of dimensions, return a pair scaled down to fit - * into the configured thumbnail square, with ratio intact, using thumb_scaling + * Fetches the thumbnails height and width settings and applies the High-DPI scaling setting before returning the dimensions. * - * #return int[] + * @return array [width, height] */ -function get_thumbnail_size_scaled(int $orig_width, int $orig_height): array -{ - global $config; - - if ($orig_width === 0) { - $orig_width = 192; - } - if ($orig_height === 0) { - $orig_height = 192; - } - - if ($orig_width > $orig_height * 5) { - $orig_width = $orig_height * 5; - } - if ($orig_height > $orig_width * 5) { - $orig_height = $orig_width * 5; - } - - $max_size = get_thumbnail_max_size_scaled(); - $max_width = $max_size[0]; - $max_height = $max_size[1]; - - $xscale = ($max_height / $orig_height); - $yscale = ($max_width / $orig_width); - $scale = ($xscale < $yscale) ? $xscale : $yscale; - - if ($scale > 1 && $config->get_bool('thumb_upscale')) { - return [(int)$orig_width, (int)$orig_height]; - } else { - return [(int)($orig_width*$scale), (int)($orig_height*$scale)]; - } -} - function get_thumbnail_max_size_scaled(): array { global $config; - $scaling = $config->get_int("thumb_scaling"); - $max_width = $config->get_int('thumb_width') * ($scaling/100); - $max_height = $config->get_int('thumb_height') * ($scaling/100); + $scaling = $config->get_int(ImageConfig::THUMB_SCALING); + $max_width = $config->get_int(ImageConfig::THUMB_WIDTH) * ($scaling/100); + $max_height = $config->get_int(ImageConfig::THUMB_HEIGHT) * ($scaling/100); return [$max_width, $max_height]; } -function create_thumbnail_convert($hash): bool -{ + +function create_image_thumb(string $hash, string $type, string $engine = null) { global $config; - $inname = warehouse_path("images", $hash); - $outname = warehouse_path("thumbs", $hash); - - $q = $config->get_int("thumb_quality"); - $convert = $config->get_string("thumb_convert_path"); - - if ($convert==null||$convert=="") { - return false; - } - - // ffff imagemagick fails sometimes, not sure why - //$format = "'%s' '%s[0]' -format '%%[fx:w] %%[fx:h]' info:"; - //$cmd = sprintf($format, $convert, $inname); - //$size = shell_exec($cmd); - //$size = explode(" ", trim($size)); + $inname = warehouse_path(Image::IMAGE_DIR, $hash); + $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); $tsize = get_thumbnail_max_size_scaled(); - $w = $tsize[0]; - $h = $tsize[1]; - - // running the call with cmd.exe requires quoting for our paths - $type = $config->get_string('thumb_type'); - - $options = ""; - if (!$config->get_bool('thumb_upscale')) { - $options .= "\>"; + if(empty($engine)) { + $engine = $config->get_string(ImageConfig::THUMB_ENGINE); } - $bg = "black"; - if ($type=="webp") { - $bg = "none"; - } - $format = '"%s" -flatten -strip -thumbnail %ux%u%s -quality %u -background %s "%s[0]" %s:"%s"'; - - $cmd = sprintf($format, $convert, $w, $h, $options, $q, $bg, $inname, $type, $outname); - $cmd = str_replace("\"convert\"", "convert", $cmd); // quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27 - exec($cmd, $output, $ret); - - log_debug('handle_pixel', "Generating thumbnail with command `$cmd`, returns $ret"); - - if ($config->get_bool("thumb_optim", false)) { - exec("jpegoptim $outname", $output, $ret); + $output_format = $config->get_string(ImageConfig::THUMB_TYPE); + if($output_format=="webp") { + $output_format = Media::WEBP_LOSSY; } - return true; + send_event(new MediaResizeEvent( + $engine, + $inname, + $type, + $outname, + $tsize[0], + $tsize[1], + false, + $output_format, + $config->get_int(ImageConfig::THUMB_QUALITY), + true, + $config->get_bool('thumb_upscale', false) + )); } -function create_thumbnail_ffmpeg($hash): bool + +const TIME_UNITS = ["s"=>60,"m"=>60,"h"=>24,"d"=>365,"y"=>PHP_INT_MAX]; +function format_milliseconds(int $input): string { - global $config; + $output = ""; - $ffmpeg = $config->get_string("thumb_ffmpeg_path"); - if ($ffmpeg==null||$ffmpeg=="") { - return false; + $remainder = floor($input / 1000); + + foreach (TIME_UNITS AS $unit=>$conversion) { + $count = $remainder % $conversion; + $remainder = floor($remainder / $conversion); + if($count==0&&$remainder<1) { + break; + } + $output = "$count".$unit." ".$output; } - $inname = warehouse_path("images", $hash); - $outname = warehouse_path("thumbs", $hash); - - $orig_size = video_size($inname); - $scaled_size = get_thumbnail_size_scaled($orig_size[0], $orig_size[1]); - - $codec = "mjpeg"; - $quality = $config->get_int("thumb_quality"); - if ($config->get_string("thumb_type")=="webp") { - $codec = "libwebp"; - } else { - // mjpeg quality ranges from 2-31, with 2 being the best quality. - $quality = floor(31 - (31 * ($quality/100))); - if ($quality<2) { - $quality = 2; - } - } - - $args = [ - escapeshellarg($ffmpeg), - "-y", "-i", escapeshellarg($inname), - "-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}", - "-f", "image2", - "-vframes", "1", - "-c:v", $codec, - "-q:v", $quality, - escapeshellarg($outname), - ]; - - $cmd = escapeshellcmd(implode(" ", $args)); - - exec($cmd, $output, $ret); - - if ((int)$ret == (int)0) { - log_debug('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret"); - return true; - } else { - log_error('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret"); - return false; - } -} - -function video_size(string $filename): array -{ - global $config; - $ffmpeg = $config->get_string("thumb_ffmpeg_path"); - $cmd = escapeshellcmd(implode(" ", [ - escapeshellarg($ffmpeg), - "-y", "-i", escapeshellarg($filename), - "-vstats" - ])); - $output = shell_exec($cmd . " 2>&1"); - // error_log("Getting size with `$cmd`"); - - $regex_sizes = "/Video: .* ([0-9]{1,4})x([0-9]{1,4})/"; - if (preg_match($regex_sizes, $output, $regs)) { - if (preg_match("/displaymatrix: rotation of (90|270).00 degrees/", $output)) { - $size = [$regs[2], $regs[1]]; - } else { - $size = [$regs[1], $regs[2]]; - } - } else { - $size = [1, 1]; - } - log_debug('imageboard/misc', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]"); - return $size; -} - -/** - * Check Memory usage limits - * - * Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024); - * New check: $memory_use = $width * $height * ($bits_per_channel) * channels * 2.5 - * - * It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4) - * We need to consider the size that we are GOING TO instead. - * - * The factor of 2.5 is simply a rough guideline. - * http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize - */ -function calc_memory_use(array $info): int -{ - if (isset($info['bits']) && isset($info['channels'])) { - $memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024; - } else { - // If we don't have bits and channel info from the image then assume default values - // of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color - $memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024; - } - return (int)$memory_use; -} - -function image_resize_gd( - String $image_filename, - array $info, - int $new_width, - int $new_height, - string $output_filename=null, - string $output_type=null, - int $output_quality = 80 -) { - $width = $info[0]; - $height = $info[1]; - - if ($output_type==null) { - /* If not specified, output to the same format as the original image */ - switch ($info[2]) { - case IMAGETYPE_GIF: $output_type = "gif"; break; - case IMAGETYPE_JPEG: $output_type = "jpeg"; break; - case IMAGETYPE_PNG: $output_type = "png"; break; - case IMAGETYPE_WEBP: $output_type = "webp"; break; - case IMAGETYPE_BMP: $output_type = "bmp"; break; - default: throw new ImageResizeException("Failed to save the new image - Unsupported image type."); - } - } - - $memory_use = calc_memory_use($info); - $memory_limit = get_memory_limit(); - if ($memory_use > $memory_limit) { - throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)"); - } - - $image = imagecreatefromstring(file_get_contents($image_filename)); - $image_resized = imagecreatetruecolor($new_width, $new_height); - try { - if ($image===false) { - throw new ImageResizeException("Could not load image: ".$image_filename); - } - if ($image_resized===false) { - throw new ImageResizeException("Could not create output image with dimensions $new_width c $new_height "); - } - - // Handle transparent images - switch ($info[2]) { - case IMAGETYPE_GIF: - $transparency = imagecolortransparent($image); - $palletsize = imagecolorstotal($image); - - // If we have a specific transparent color - if ($transparency >= 0 && $transparency < $palletsize) { - // Get the original image's transparent color's RGB values - $transparent_color = imagecolorsforindex($image, $transparency); - - // Allocate the same color in the new image resource - $transparency = imagecolorallocate($image_resized, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); - if ($transparency===false) { - throw new ImageResizeException("Unable to allocate transparent color"); - } - - // Completely fill the background of the new image with allocated color. - if (imagefill($image_resized, 0, 0, $transparency)===false) { - throw new ImageResizeException("Unable to fill new image with transparent color"); - } - - // Set the background color for new image to transparent - imagecolortransparent($image_resized, $transparency); - } - break; - case IMAGETYPE_PNG: - case IMAGETYPE_WEBP: - // - // More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php - // - if (imagealphablending($image_resized, false)===false) { - throw new ImageResizeException("Unable to disable image alpha blending"); - } - if (imagesavealpha($image_resized, true)===false) { - throw new ImageResizeException("Unable to enable image save alpha"); - } - $transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127); - if ($transparent_color===false) { - throw new ImageResizeException("Unable to allocate transparent color"); - } - if (imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color)===false) { - throw new ImageResizeException("Unable to fill new image with transparent color"); - } - break; - } - - // Actually resize the image. - if (imagecopyresampled( - $image_resized, - $image, - 0, - 0, - 0, - 0, - $new_width, - $new_height, - $width, - $height - )===false) { - throw new ImageResizeException("Unable to copy resized image data to new image"); - } - - $result = false; - switch ($output_type) { - case "bmp": - $result = imagebmp($image_resized, $output_filename, true); - break; - case "webp": - $result = imagewebp($image_resized, $output_filename, $output_quality); - break; - case "jpg": - case "jpeg": - $result = imagejpeg($image_resized, $output_filename, $output_quality); - break; - case "png": - $result = imagepng($image_resized, $output_filename, 9); - break; - case "gif": - $result = imagegif($image_resized, $output_filename); - break; - default: - throw new ImageResizeException("Failed to save the new image - Unsupported image type: $output_type"); - } - if ($result==false) { - throw new ImageResizeException("Failed to save the new image, function returned false when saving type: $output_type"); - } - } finally { - imagedestroy($image); - imagedestroy($image_resized); - } -} - -function is_animated_gif(String $image_filename) -{ - $isanigif = 0; - if (($fh = @fopen($image_filename, 'rb'))) { - //check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473) - while (!feof($fh) && $isanigif < 2) { - $chunk = fread($fh, 1024 * 100); - $isanigif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); - } - } - return ($isanigif == 0); -} + return trim($output); +} \ No newline at end of file diff --git a/core/imageboard/search.php b/core/imageboard/search.php index e3c63940..2960663f 100644 --- a/core/imageboard/search.php +++ b/core/imageboard/search.php @@ -29,7 +29,7 @@ class Querylet } } -class TagQuerylet +class TagCondition { /** @var string */ public $tag; @@ -43,7 +43,7 @@ class TagQuerylet } } -class ImgQuerylet +class ImgCondition { /** @var Querylet */ public $qlet; diff --git a/core/imageboard/tag.php b/core/imageboard/tag.php index 3e32d524..ddce54f6 100644 --- a/core/imageboard/tag.php +++ b/core/imageboard/tag.php @@ -100,4 +100,12 @@ class Tag return $tag_array; } + + public static function sqlify(string $term): string + { + $term = str_replace('_', '\_', $term); + $term = str_replace('%', '\%', $term); + $term = str_replace('*', '%', $term); + return $term; + } } diff --git a/core/page.php b/core/page.php index e9b3384b..7efa2833 100644 --- a/core/page.php +++ b/core/page.php @@ -26,6 +26,13 @@ * Various other common functions are available as part of the Themelet class. */ +abstract class PageMode +{ + const REDIRECT = 'redirect'; + const DATA = 'data'; + const PAGE = 'page'; + const FILE = 'file'; +} /** * Class Page @@ -40,7 +47,7 @@ class Page /** @name Overall */ //@{ /** @var string */ - public $mode = "page"; + public $mode = PageMode::PAGE; /** @var string */ public $type = "text/html; charset=utf-8"; @@ -69,9 +76,14 @@ class Page /** @var string; public only for unit test */ public $data = ""; + /** @var string; */ + public $file = null; + /** @var string; public only for unit test */ public $filename = null; + private $disposition = null; + /** * Set the raw data to be sent. */ @@ -80,12 +92,18 @@ class Page $this->data = $data; } + public function set_file(string $file): void + { + $this->file = $file; + } + /** * Set the recommended download filename. */ - public function set_filename(string $filename): void + public function set_filename(string $filename, string $disposition = "attachment"): void { $this->filename = $filename; + $this->disposition = $disposition; } @@ -165,7 +183,7 @@ class Page /** * Add a line to the HTML head section. */ - public function add_html_header(string $line, int $position=50): void + public function add_html_header(string $line, int $position = 50): void { while (isset($this->html_headers[$position])) { $position++; @@ -176,7 +194,7 @@ class Page /** * Add a http header to be sent to the client. */ - public function add_http_header(string $line, int $position=50): void + public function add_http_header(string $line, int $position = 50): void { while (isset($this->http_headers[$position])) { $position++; @@ -191,13 +209,13 @@ class Page */ public function add_cookie(string $name, string $value, int $time, string $path): void { - $full_name = COOKIE_PREFIX."_".$name; + $full_name = COOKIE_PREFIX . "_" . $name; $this->cookies[] = [$full_name, $value, $time, $path]; } public function get_cookie(string $name): ?string { - $full_name = COOKIE_PREFIX."_".$name; + $full_name = COOKIE_PREFIX . "_" . $name; if (isset($_COOKIE[$full_name])) { return $_COOKIE[$full_name]; } else { @@ -246,8 +264,8 @@ class Page global $page, $user; header("HTTP/1.0 {$this->code} Shimmie"); - header("Content-type: ".$this->type); - header("X-Powered-By: SCore-".SCORE_VERSION); + header("Content-type: " . $this->type); + header("X-Powered-By: SCore-" . SCORE_VERSION); if (!headers_sent()) { foreach ($this->http_headers as $head) { @@ -261,7 +279,7 @@ class Page } switch ($this->mode) { - case "page": + case PageMode::PAGE: if (CACHE_HTTP) { header("Vary: Cookie, Accept-Encoding"); if ($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") { @@ -281,20 +299,135 @@ class Page $this->add_cookie("flash_message", "", -1, "/"); } usort($this->blocks, "blockcmp"); + $pnbe = new PageNavBuildingEvent(); + send_event($pnbe); + + $nav_links = $pnbe->links; + + $active_link = null; + // To save on event calls, we check if one of the top-level links has already been marked as active + foreach ($nav_links as $link) { + if($link->active===true) { + $active_link = $link; + break; + } + } + $sub_links = null; + // If one is, we just query for sub-menu options under that one tab + if($active_link!==null) { + $psnbe = new PageSubNavBuildingEvent($active_link->name); + send_event($psnbe); + $sub_links = $psnbe->links; + } else { + // Otherwise we query for the sub-items under each of the tabs + foreach ($nav_links as $link) { + $psnbe = new PageSubNavBuildingEvent($link->name); + send_event($psnbe); + + // Now we check for a current link so we can identify the sub-links to show + foreach ($psnbe->links as $sub_link) { + if($sub_link->active===true) { + $sub_links = $psnbe->links; + break; + } + } + // If the active link has been detected, we break out + if($sub_links!==null) { + $link->active = true; + break; + } + } + } + + + + $sub_links = $sub_links??[]; + usort($nav_links, "sort_nav_links"); + usort($sub_links, "sort_nav_links"); + $this->add_auto_html_headers(); $layout = new Layout(); - $layout->display_page($page); + $layout->display_page($page, $nav_links, $sub_links); break; - case "data": - header("Content-Length: ".strlen($this->data)); + case PageMode::DATA: + header("Content-Length: " . strlen($this->data)); if (!is_null($this->filename)) { - header('Content-Disposition: attachment; filename='.$this->filename); + header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename); } print $this->data; break; - case "redirect": - header('Location: '.$this->redirect); - print 'You should be redirected to '.$this->redirect.''; + case PageMode::FILE: + if (!is_null($this->filename)) { + header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename); + } + + //https://gist.github.com/codler/3906826 + + $size = filesize($this->file); // File size + $length = $size; // Content length + $start = 0; // Start byte + $end = $size - 1; // End byte + + header("Content-Length: " . strlen($size)); + header('Accept-Ranges: bytes'); + + if (isset($_SERVER['HTTP_RANGE'])) { + + $c_start = $start; + $c_end = $end; + list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2); + if (strpos($range, ',') !== false) { + header('HTTP/1.1 416 Requested Range Not Satisfiable'); + header("Content-Range: bytes $start-$end/$size"); + break; + } + if ($range == '-') { + $c_start = $size - substr($range, 1); + } else { + $range = explode('-', $range); + $c_start = $range[0]; + $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size; + } + $c_end = ($c_end > $end) ? $end : $c_end; + if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) { + header('HTTP/1.1 416 Requested Range Not Satisfiable'); + header("Content-Range: bytes $start-$end/$size"); + break; + } + $start = $c_start; + $end = $c_end; + $length = $end - $start + 1; + header('HTTP/1.1 206 Partial Content'); + } + header("Content-Range: bytes $start-$end/$size"); + header("Content-Length: " . $length); + + + $fp = fopen($this->file, 'r'); + try { + fseek($fp, $start); + $buffer = 1024 * 64; + while (!feof($fp) && ($p = ftell($fp)) <= $end) { + if ($p + $buffer > $end) { + $buffer = $end - $p + 1; + } + set_time_limit(0); + echo fread($fp, $buffer); + flush(); + + // After flush, we can tell if the client browser has disconnected. + // This means we can start sending a large file, and if we detect they disappeared + // then we can just stop and not waste any more resources or bandwidth. + if (connection_status() != 0) + break; + } + } finally { + fclose($fp); + } + break; + case PageMode::REDIRECT: + header('Location: ' . $this->redirect); + print 'You should be redirected to ' . $this->redirect . ''; break; default: print "Invalid page mode"; @@ -318,7 +451,7 @@ class Page global $config; $data_href = get_base_href(); - $theme_name = $config->get_string('theme', 'default'); + $theme_name = $config->get_string(SetupConfig::THEME, 'default'); $this->add_html_header("", 40); @@ -335,7 +468,7 @@ class Page /*** Generate CSS cache files ***/ $css_latest = $config_latest; $css_files = array_merge( - zglob("ext/{".ENABLED_EXTS."}/style.css"), + zglob("ext/{" . ENABLED_EXTS . "}/style.css"), zglob("themes/$theme_name/style.css") ); foreach ($css_files as $css) { @@ -348,7 +481,7 @@ class Page foreach ($css_files as $file) { $file_data = file_get_contents($file); $pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/'; - $replace = 'url("../../../'.dirname($file).'/$1")'; + $replace = 'url("../../../' . dirname($file) . '/$1")'; $file_data = preg_replace($pattern, $replace, $file_data); $css_data .= $file_data . "\n"; } @@ -366,7 +499,7 @@ class Page "vendor/bower-asset/js-cookie/src/js.cookie.js", "ext/handle_static/modernizr-3.3.1.custom.js", ], - zglob("ext/{".ENABLED_EXTS."}/script.js"), + zglob("ext/{" . ENABLED_EXTS . "}/script.js"), zglob("themes/$theme_name/script.js") ); foreach ($js_files as $js) { @@ -384,3 +517,99 @@ class Page $this->add_html_header("", 44); } } + +class PageNavBuildingEvent extends Event +{ + public $links = []; + + public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50) + { + $this->links[] = new NavLink($name, $link, $desc, $active, $order); + } +} + +class PageSubNavBuildingEvent extends Event +{ + public $parent; + + public $links = []; + + public function __construct(string $parent) + { + $this->parent= $parent; + } + + public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50) + { + $this->links[] = new NavLink($name, $link, $desc, $active,$order); + } +} + +class NavLink +{ + public $name; + public $link; + public $description; + public $order; + public $active = false; + + public function __construct(String $name, Link $link, String $description, ?bool $active = null, int $order = 50) + { + global $config; + + $this->name = $name; + $this->link = $link; + $this->description = $description; + $this->order = $order; + if($active==null) { + $query = ltrim(_get_query(), "/"); + if ($query === "") { + // This indicates the front page, so we check what's set as the front page + $front_page = trim($config->get_string(SetupConfig::FRONT_PAGE), "/"); + + if ($front_page === $link->page) { + $this->active = true; + } else { + $this->active = self::is_active([$link->page], $front_page); + } + } elseif($query===$link->page) { + $this->active = true; + }else { + $this->active = self::is_active([$link->page]); + } + } else { + $this->active = $active; + } + + } + + public static function is_active(array $pages_matched, string $url = null): bool + { + /** + * Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.) + */ + $url = $url??ltrim(_get_query(), "/"); + + $re1='.*?'; + $re2='((?:[a-z][a-z_]+))'; + + if (preg_match_all("/".$re1.$re2."/is", $url, $matches)) { + $url=$matches[1][0]; + } + + $count_pages_matched = count($pages_matched); + + for ($i=0; $i < $count_pages_matched; $i++) { + if ($url == $pages_matched[$i]) { + return true; + } + } + + return false; + } +} + +function sort_nav_links(NavLink $a, NavLink $b) +{ + return $a->order - $b->order; +} diff --git a/core/permissions.php b/core/permissions.php new file mode 100644 index 00000000..4378a9c0 --- /dev/null +++ b/core/permissions.php @@ -0,0 +1,70 @@ + 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png', - 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon', - 'swf' => 'application/x-shockwave-flash', 'video/x-flv' => 'flv', - 'svg' => 'image/svg+xml', 'pdf' => 'application/pdf', - 'zip' => 'application/zip', 'gz' => 'application/x-gzip', - 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip', - 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain', - 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html', - 'css' => 'text/css', 'js' => 'text/javascript', - 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml', - 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav', - 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', - 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php', - 'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm', - 'webp' => 'image/webp', 'bmp' =>'image/x-ms-bmp', 'psd' => 'image/vnd.adobe.photoshop', + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'ico' => 'image/x-icon', + 'swf' => 'application/x-shockwave-flash', + 'flv' => 'video/x-flv', + 'svg' => 'image/svg+xml', + 'pdf' => 'application/pdf', + 'zip' => 'application/zip', + 'gz' => 'application/x-gzip', + 'tar' => 'application/x-tar', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'txt' => 'text/plain', + 'asc' => 'text/plain', + 'htm' => 'text/html', + 'html' => 'text/html', + 'css' => 'text/css', + 'js' => 'text/javascript', + 'xml' => 'text/xml', + 'xsl' => 'application/xsl+xml', + 'ogg' => 'application/ogg', + 'mp3' => 'audio/mpeg', + 'wav' => 'audio/x-wav', + 'avi' => 'video/x-msvideo', + 'mpg' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mov' => 'video/quicktime', + 'flv' => 'video/x-flv', + 'php' => 'text/x-php', + 'mp4' => 'video/mp4', + 'ogv' => 'video/ogg', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'bmp' =>'image/x-ms-bmp', + 'psd' => 'image/vnd.adobe.photoshop', 'mkv' => 'video/x-matroska' ]; @@ -729,3 +752,53 @@ function validate_input(array $inputs): array return $outputs; } + +/** + * Translates all possible directory separators to the appropriate one for the current system, + * and removes any duplicate separators. + */ +function sanitize_path(string $path): string +{ + return preg_replace('|[\\\\/]+|S',DIRECTORY_SEPARATOR,$path); +} + +/** + * Combines all path segments specified, ensuring no duplicate separators occur, + * as well as converting all possible separators to the one appropriate for the current system. + */ +function join_path(string ...$paths): string +{ + $output = ""; + foreach ($paths as $path) { + if(empty($path)) { + continue; + } + $path = sanitize_path($path); + if(empty($output)) { + $output = $path; + } else { + $output = rtrim($output, DIRECTORY_SEPARATOR); + $path = ltrim($path, DIRECTORY_SEPARATOR); + $output .= DIRECTORY_SEPARATOR . $path; + } + } + return $output; +} + +/** + * Perform callback on each item returned by an iterator. + */ +function iterator_map(callable $callback, iterator $iter): Generator +{ + foreach($iter as $i) { + yield call_user_func($callback,$i); + } +} + +/** + * Perform callback on each item returned by an iterator and combine the result into an array. + */ +function iterator_map_to_array(callable $callback, iterator $iter): array +{ + return iterator_to_array(iterator_map($callback, $iter)); +} \ No newline at end of file diff --git a/core/send_event.php b/core/send_event.php index 6903a03c..24897658 100644 --- a/core/send_event.php +++ b/core/send_event.php @@ -9,9 +9,7 @@ $_shm_event_listeners = []; function _load_event_listeners(): void { - global $_shm_event_listeners, $_shm_ctx; - - $_shm_ctx->log_start("Loading extensions"); + global $_shm_event_listeners; $cache_path = data_path("cache/shm_event_listeners.php"); if (COMPILE_ELS && file_exists($cache_path)) { @@ -23,8 +21,13 @@ function _load_event_listeners(): void _dump_event_listeners($_shm_event_listeners, $cache_path); } } +} - $_shm_ctx->log_endok(); +function _clear_cached_event_listeners(): void +{ + if (file_exists(data_path("cache/shm_event_listeners.php"))) { + unlink(data_path("cache/shm_event_listeners.php")); + } } function _set_event_listeners(): void @@ -105,35 +108,31 @@ $_shm_event_count = 0; */ function send_event(Event $event): void { - global $_shm_event_listeners, $_shm_event_count, $_shm_ctx; + global $tracer_enabled; + + global $_shm_event_listeners, $_shm_event_count, $_tracer; if (!isset($_shm_event_listeners[get_class($event)])) { return; } $method_name = "on".str_replace("Event", "", get_class($event)); // send_event() is performance sensitive, and with the number - // of times context gets called the time starts to add up - $ctx_enabled = constant('CONTEXT'); - - if ($ctx_enabled) { - $_shm_ctx->log_start(get_class($event)); - } + // of times tracer gets called the time starts to add up + if ($tracer_enabled) $_tracer->begin(get_class($event)); // SHIT: http://bugs.php.net/bug.php?id=35106 $my_event_listeners = $_shm_event_listeners[get_class($event)]; ksort($my_event_listeners); + foreach ($my_event_listeners as $listener) { - if ($ctx_enabled) { - $_shm_ctx->log_start(get_class($listener)); - } + if ($tracer_enabled) $_tracer->begin(get_class($listener)); if (method_exists($listener, $method_name)) { $listener->$method_name($event); } - if ($ctx_enabled) { - $_shm_ctx->log_endok(); + if ($tracer_enabled) $_tracer->end(); + if($event->stop_processing===true) { + break; } } $_shm_event_count++; - if ($ctx_enabled) { - $_shm_ctx->log_endok(); - } + if ($tracer_enabled) $_tracer->end(); } diff --git a/core/sys_config.php b/core/sys_config.php index a2fbefee..5dc80459 100644 --- a/core/sys_config.php +++ b/core/sys_config.php @@ -27,12 +27,10 @@ function _d(string $name, $value): void } _d("DATABASE_DSN", null); // string PDO database connection details _d("DATABASE_KA", true); // string Keep database connection alive +_d("DATABASE_TIMEOUT", 10000);// int Time to wait for each statement to complete _d("CACHE_DSN", null); // string cache connection details _d("DEBUG", false); // boolean print various debugging details -_d("DEBUG_SQL", false); // boolean dump SQL queries to data/sql.log -_d("DEBUG_CACHE", false); // boolean dump cache queries to data/cache.log _d("COVERAGE", false); // boolean activate xdebug coverage monitor -_d("CONTEXT", null); // string file to log performance data into _d("CACHE_HTTP", false); // boolean output explicit HTTP caching headers _d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-shared logins, give them different prefixes _d("SPEED_HAX", false); // boolean do some questionable things in the name of performance @@ -42,11 +40,12 @@ _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.7-beta'); // 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,handle_static,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable +_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor,media,help_pages,system"); // extensions to always enable _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", '7.1');// string minimum supported PHP version -_d("SLOW_PAGES", null); // float log pages which take more time than this +_d("TRACE_FILE", null); // string file to log performance data into +_d("TRACE_THRESHOLD", 0.0); // float log pages which take more time than this many seconds _d("ENABLED_MODS", "imageboard"); /* diff --git a/core/tests/polyfills.test.php b/core/tests/polyfills.test.php index 7aadec3d..68e0d10a 100644 --- a/core/tests/polyfills.test.php +++ b/core/tests/polyfills.test.php @@ -45,4 +45,77 @@ class PolyfillsTest extends \PHPUnit\Framework\TestCase $this->assertEquals(parse_shorthand_int("43.4KB"), 44441); $this->assertEquals(parse_shorthand_int("1231231231"), 1231231231); } + + public function test_sanitize_path() + { + + $this->assertEquals( + "one", + sanitize_path("one") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two", + sanitize_path("one\\two") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two", + sanitize_path("one/two") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two", + sanitize_path("one\\\\two") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two", + sanitize_path("one//two") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two", + sanitize_path("one\\\\\\two") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two", + sanitize_path("one///two") + ); + + $this->assertEquals( + DIRECTORY_SEPARATOR."one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR, + sanitize_path("\\/one/\\/\\/two\\/") + ); + + } + + public function test_join_path() + { + $this->assertEquals( + "one", + join_path("one") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two", + join_path("one","two") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three", + join_path("one","two","three") + ); + + $this->assertEquals( + "one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three", + join_path("one/two","three") + ); + + $this->assertEquals( + DIRECTORY_SEPARATOR."one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three".DIRECTORY_SEPARATOR, + join_path("\\/////\\\\one/\///"."\\//two\/\\//\\//","//\/\\\/three/\\/\/") + ); + } } diff --git a/core/tests/util.test.php b/core/tests/util.test.php new file mode 100644 index 00000000..3b3381f7 --- /dev/null +++ b/core/tests/util.test.php @@ -0,0 +1,67 @@ +assertEquals( + join_path(DATA_DIR,"base",$hash), + warehouse_path("base",$hash,false, 0) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a",$hash), + warehouse_path("base",$hash,false, 1) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1",$hash), + warehouse_path("base",$hash,false, 2) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1","9c",$hash), + warehouse_path("base",$hash,false, 3) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1","9c","10",$hash), + warehouse_path("base",$hash,false, 4) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1","9c","10","d6",$hash), + warehouse_path("base",$hash,false, 5) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85",$hash), + warehouse_path("base",$hash,false, 6) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85","94",$hash), + warehouse_path("base",$hash,false, 7) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85","94","15",$hash), + warehouse_path("base",$hash,false, 8) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85","94","15",$hash), + warehouse_path("base",$hash,false, 9) + ); + + $this->assertEquals( + join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85","94","15",$hash), + warehouse_path("base",$hash,false, 10) + ); + + } +} diff --git a/core/urls.php b/core/urls.php index e4fc0977..457bfe1b 100644 --- a/core/urls.php +++ b/core/urls.php @@ -3,6 +3,23 @@ * HTML Generation * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +class Link +{ + public $page; + public $query; + + public function __construct(?string $page=null, ?string $query=null) + { + $this->page = $page; + $this->query = $query; + } + + public function make_link(): string + { + return make_link($this->page, $this->query); + } +} + /** * Figure out the correct way to link to a page, taking into account * things like the nice URLs setting. @@ -14,7 +31,7 @@ function make_link(?string $page=null, ?string $query=null): string global $config; if (is_null($page)) { - $page = $config->get_string('main_page'); + $page = $config->get_string(SetupConfig::MAIN_PAGE); } if (!is_null(BASE_URL)) { diff --git a/core/user.php b/core/user.php index 098c7723..05f6e7f7 100644 --- a/core/user.php +++ b/core/user.php @@ -69,7 +69,7 @@ class User global $config, $database; $row = $database->cache->get("user-session:$name-$session"); if (!$row) { - if ($database->get_driver_name() === "mysql") { + if ($database->get_driver_name() === DatabaseDriver::MYSQL) { $query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess"; } else { $query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess"; diff --git a/core/userclass.php b/core/userclass.php index 55be5e91..a66ad6f7 100644 --- a/core/userclass.php +++ b/core/userclass.php @@ -72,129 +72,138 @@ class UserClass // action = create / view / edit / delete // object = image / user / tag / setting new UserClass("base", null, [ - "change_setting" => false, # modify web-level settings, eg the config table - "override_config" => false, # modify sys-level settings, eg shimmie.conf.php - "big_search" => false, # search for more than 3 tags at once (speed mode only) + Permissions::CHANGE_SETTING => false, # modify web-level settings, eg the config table + Permissions::OVERRIDE_CONFIG => false, # modify sys-level settings, eg shimmie.conf.php + Permissions::BIG_SEARCH => false, # search for more than 3 tags at once (speed mode only) - "manage_extension_list" => false, - "manage_alias_list" => false, - "mass_tag_edit" => false, + Permissions::MANAGE_EXTENSION_LIST => false, + Permissions::MANAGE_ALIAS_LIST => false, + Permissions::MASS_TAG_EDIT => false, - "view_ip" => false, # view IP addresses associated with things - "ban_ip" => false, + Permissions::VIEW_IP => false, # view IP addresses associated with things + Permissions::BAN_IP => false, - "edit_user_name" => false, - "edit_user_password" => false, - "edit_user_info" => false, # email address, etc - "edit_user_class" => false, - "delete_user" => false, + Permissions::EDIT_USER_NAME => false, + Permissions::EDIT_USER_PASSWORD => false, + Permissions::EDIT_USER_INFO => false, # email address, etc + Permissions::EDIT_USER_CLASS => false, + Permissions::DELETE_USER => false, - "create_comment" => false, - "delete_comment" => false, - "bypass_comment_checks" => false, # spam etc + Permissions::CREATE_COMMENT => false, + Permissions::DELETE_COMMENT => false, + Permissions::BYPASS_COMMENT_CHECKS => false, # spam etc - "replace_image" => false, - "create_image" => false, - "edit_image_tag" => false, - "edit_image_source" => false, - "edit_image_owner" => false, - "edit_image_lock" => false, - "bulk_edit_image_tag" => false, - "bulk_edit_image_source" => false, - "delete_image" => false, + Permissions::REPLACE_IMAGE => false, + Permissions::CREATE_IMAGE => false, + Permissions::EDIT_IMAGE_TAG => false, + Permissions::EDIT_IMAGE_SOURCE => false, + Permissions::EDIT_IMAGE_OWNER => false, + Permissions::EDIT_IMAGE_LOCK => false, + Permissions::EDIT_IMAGE_TITLE => false, + Permissions::BULK_EDIT_IMAGE_TAG => false, + Permissions::BULK_EDIT_IMAGE_SOURCE => false, + Permissions::DELETE_IMAGE => false, - "ban_image" => false, + Permissions::BAN_IMAGE => false, - "view_eventlog" => false, - "ignore_downtime" => false, + Permissions::VIEW_EVENTLOG => false, + Permissions::IGNORE_DOWNTIME => false, - "create_image_report" => false, - "view_image_report" => false, # deal with reported images + Permissions::CREATE_IMAGE_REPORT => false, + Permissions::VIEW_IMAGE_REPORT => false, # deal with reported images - "edit_wiki_page" => false, - "delete_wiki_page" => false, + Permissions::EDIT_WIKI_PAGE => false, + Permissions::DELETE_WIKI_PAGE => false, - "manage_blocks" => false, + Permissions::MANAGE_BLOCKS => false, - "manage_admintools" => false, + Permissions::MANAGE_ADMINTOOLS => false, - "view_other_pms" => false, - "edit_feature" => false, - "bulk_edit_vote" => false, - "edit_other_vote" => false, - "view_sysinfo" => false, + Permissions::VIEW_OTHER_PMS => false, + Permissions::EDIT_FEATURE => false, + Permissions::BULK_EDIT_VOTE => false, + Permissions::EDIT_OTHER_VOTE => false, + Permissions::VIEW_SYSINTO => false, - "hellbanned" => false, - "view_hellbanned" => false, + Permissions::HELLBANNED => false, + Permissions::VIEW_HELLBANNED => false, - "protected" => false, # only admins can modify protected users (stops a moderator changing an admin's password) + Permissions::PROTECTED => false, # only admins can modify protected users (stops a moderator changing an admin's password) - "edit_image_rating" => false, - "bulk_edit_image_rating" => false, + Permissions::EDIT_IMAGE_RATING => false, + Permissions::BULK_EDIT_IMAGE_RATING => false, + Permissions::VIEW_TRASH => false, + + Permissions::PERFORM_BULK_ACTIONS => false, ]); new UserClass("anonymous", "base", [ ]); new UserClass("user", "base", [ - "big_search" => true, - "create_image" => true, - "create_comment" => true, - "edit_image_tag" => true, - "edit_image_source" => true, - "create_image_report" => true, - "edit_image_rating" => true, + Permissions::BIG_SEARCH => true, + Permissions::CREATE_IMAGE => true, + Permissions::CREATE_COMMENT => true, + Permissions::EDIT_IMAGE_TAG => true, + Permissions::EDIT_IMAGE_SOURCE => true, + Permissions::EDIT_IMAGE_TITLE => true, + Permissions::CREATE_IMAGE_REPORT => true, + Permissions::EDIT_IMAGE_RATING => true, + ]); new UserClass("admin", "base", [ - "change_setting" => true, - "override_config" => true, - "big_search" => true, - "edit_image_lock" => true, - "view_ip" => true, - "ban_ip" => true, - "edit_user_name" => true, - "edit_user_password" => true, - "edit_user_info" => true, - "edit_user_class" => true, - "delete_user" => true, - "create_image" => true, - "delete_image" => true, - "ban_image" => true, - "create_comment" => true, - "delete_comment" => true, - "bypass_comment_checks" => true, - "replace_image" => true, - "manage_extension_list" => true, - "manage_alias_list" => true, - "edit_image_tag" => true, - "edit_image_source" => true, - "edit_image_owner" => true, - "bulk_edit_image_tag" => true, - "bulk_edit_image_source" => true, - "mass_tag_edit" => true, - "create_image_report" => true, - "view_image_report" => true, - "edit_wiki_page" => true, - "delete_wiki_page" => true, - "view_eventlog" => true, - "manage_blocks" => true, - "manage_admintools" => true, - "ignore_downtime" => true, - "view_other_pms" => true, - "edit_feature" => true, - "bulk_edit_vote" => true, - "edit_other_vote" => true, - "view_sysinfo" => true, - "view_hellbanned" => true, - "protected" => true, - "edit_image_rating" => true, - "bulk_edit_image_rating" => true, + Permissions::CHANGE_SETTING => true, + Permissions::OVERRIDE_CONFIG => true, + Permissions::BIG_SEARCH => true, + Permissions::EDIT_IMAGE_LOCK => true, + Permissions::VIEW_IP => true, + Permissions::BAN_IP => true, + Permissions::EDIT_USER_NAME => true, + Permissions::EDIT_USER_PASSWORD => true, + Permissions::EDIT_USER_INFO => true, + Permissions::EDIT_USER_CLASS => true, + Permissions::DELETE_USER => true, + Permissions::CREATE_IMAGE => true, + Permissions::DELETE_IMAGE => true, + Permissions::BAN_IMAGE => true, + Permissions::CREATE_COMMENT => true, + Permissions::DELETE_COMMENT => true, + Permissions::BYPASS_COMMENT_CHECKS => true, + Permissions::REPLACE_IMAGE => true, + Permissions::MANAGE_EXTENSION_LIST => true, + Permissions::MANAGE_ALIAS_LIST => true, + Permissions::EDIT_IMAGE_TAG => true, + Permissions::EDIT_IMAGE_SOURCE => true, + Permissions::EDIT_IMAGE_OWNER => true, + Permissions::EDIT_IMAGE_TITLE => true, + Permissions::BULK_EDIT_IMAGE_TAG => true, + Permissions::BULK_EDIT_IMAGE_SOURCE => true, + Permissions::MASS_TAG_EDIT => true, + Permissions::CREATE_IMAGE_REPORT => true, + Permissions::VIEW_IMAGE_REPORT => true, + Permissions::EDIT_WIKI_PAGE => true, + Permissions::DELETE_WIKI_PAGE => true, + Permissions::VIEW_EVENTLOG => true, + Permissions::MANAGE_BLOCKS => true, + Permissions::MANAGE_ADMINTOOLS => true, + Permissions::IGNORE_DOWNTIME => true, + Permissions::VIEW_OTHER_PMS => true, + Permissions::EDIT_FEATURE => true, + Permissions::BULK_EDIT_VOTE => true, + Permissions::EDIT_OTHER_VOTE => true, + Permissions::VIEW_SYSINTO => true, + Permissions::VIEW_HELLBANNED => true, + Permissions::PROTECTED => true, + Permissions::EDIT_IMAGE_RATING => true, + Permissions::BULK_EDIT_IMAGE_RATING => true, + Permissions::VIEW_TRASH => true, + Permissions::PERFORM_BULK_ACTIONS => true, ]); new UserClass("hellbanned", "user", [ - "hellbanned" => true, + Permissions::HELLBANNED => true, ]); @include_once "data/config/user-classes.conf.php"; diff --git a/core/util.php b/core/util.php index 299463d8..7017078d 100644 --- a/core/util.php +++ b/core/util.php @@ -1,10 +1,11 @@ get_string("theme", "default"); + $theme = $config->get_string(SetupConfig::THEME, "default"); if (!file_exists("themes/$theme")) { $theme = "default"; } @@ -78,7 +79,7 @@ function get_memory_limit(): int // thumbnail generation requires lots of memory $default_limit = 8*1024*1024; // 8 MB of memory is PHP's default. - $shimmie_limit = parse_shorthand_int($config->get_int("thumb_mem_limit")); + $shimmie_limit = parse_shorthand_int($config->get_int(MediaConfig::MEM_LIMIT)); if ($shimmie_limit < 3*1024*1024) { // we aren't going to fit, override @@ -159,25 +160,40 @@ function format_text(string $string): string return $tfe->formatted; } -function warehouse_path(string $base, string $hash, bool $create=true): string +/** + * Generates the path to a file under the data folder based on the file's hash. + * This process creates subfolders based on octet pairs from the file's hash. + * The calculated folder follows this pattern data/$base/octet_pairs/$hash + * @param string $base + * @param string $hash + * @param bool $create + * @param int $splits The number of octet pairs to split the hash into. Caps out at strlen($hash)/2. + * @return string + */ +function warehouse_path(string $base, string $hash, bool $create=true, int $splits = WH_SPLITS): string { - $ab = substr($hash, 0, 2); - $cd = substr($hash, 2, 2); - if (WH_SPLITS == 2) { - $pa = 'data/'.$base.'/'.$ab.'/'.$cd.'/'.$hash; - } else { - $pa = 'data/'.$base.'/'.$ab.'/'.$hash; + $dirs =[DATA_DIR, $base]; + $splits = min($splits, strlen($hash) / 2); + for($i = 0; $i < $splits; $i++) { + $dirs[] = substr($hash, $i * 2, 2); } + $dirs[] = $hash; + + $pa = join_path(...$dirs); + if ($create && !file_exists(dirname($pa))) { mkdir(dirname($pa), 0755, true); } return $pa; } -function data_path(string $filename): string +/** + * Determines the path to the specified file in the data folder. + */ +function data_path(string $filename, bool $create = true): string { - $filename = "data/" . $filename; - if (!file_exists(dirname($filename))) { + $filename = join_path("data", $filename); + if ($create&&!file_exists(dirname($filename))) { mkdir(dirname($filename), 0755, true); } return $filename; @@ -281,21 +297,57 @@ function manual_include(string $fname): ?string function path_to_tags(string $path): string { $matches = []; - $tags = ""; - if (preg_match("/\d+ - (.*)\.([a-zA-Z0-9]+)/", basename($path), $matches)) { - $tags = $matches[1]; + $tags = []; + if (preg_match("/\d+ - (.+)\.([a-zA-Z0-9]+)/", basename($path), $matches)) { + $tags = explode(" ", $matches[1]); } + + $path = dirname($path); + $path = str_replace(";", ":", $path); + $path = str_replace("__", " ", $path); - $dir_tags = dirname($path); - $dir_tags = str_replace("/", " ", $dir_tags); - $dir_tags = str_replace("__", " ", $dir_tags); - $dir_tags = trim($dir_tags); - if ($dir_tags != "") { - $tags = trim($tags)." ".trim($dir_tags); + + $category = ""; + foreach (explode("/", $path) as $dir) { + $category_to_inherit = ""; + foreach (explode(" ", $dir) as $tag) { + $tag = trim($tag); + if ($tag=="") { + continue; + } + if (substr_compare($tag, ":", -1) === 0) { + // This indicates a tag that ends in a colon, + // which is for inheriting to tags on the subfolder + $category_to_inherit = $tag; + } else { + if ($category!=""&&strpos($tag, ":") === false) { + // This indicates that category inheritance is active, + // and we've encountered a tag that does not specify a category. + // So we attach the inherited category to the tag. + $tag = $category.$tag; + } + $tags[] = $tag; + } + } + // Category inheritance only works on the immediate subfolder, + // so we hold a category until the next iteration, and then set + // it back to an empty string after that iteration + $category = $category_to_inherit; } - $tags = trim($tags); - - return $tags; + + return implode(" ", $tags); +} + + +function join_url(string $base, string ...$paths) +{ + $output = $base; + foreach ($paths as $path) { + $output = rtrim($output,"/"); + $path = ltrim($path, "/"); + $output .= "/".$path; + } + return $output; } @@ -338,19 +390,6 @@ function get_debug_info(): string return $debug; } -function log_slow(): void -{ - global $_shm_load_start; - if (!is_null(SLOW_PAGES)) { - $_time = microtime(true) - $_shm_load_start; - if ($_time > SLOW_PAGES) { - $_query = _get_query(); - $_dbg = get_debug_info(); - file_put_contents("data/slow-pages.log", "$_time $_query $_dbg\n", FILE_APPEND | LOCK_EX); - } - } -} - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * Request initialisation stuff * @@ -375,7 +414,7 @@ date and you should plan on moving elsewhere. function _sanitise_environment(): void { - global $_shm_ctx; + global $_tracer; if (TIMEZONE) { date_default_timezone_set(TIMEZONE); @@ -387,10 +426,7 @@ function _sanitise_environment(): void error_reporting(E_ALL); } - $_shm_ctx = new Context(); - if (CONTEXT) { - $_shm_ctx->set_log(CONTEXT); - } + $_tracer = new EventTracer(); if (COVERAGE) { _start_coverage(); @@ -552,8 +588,8 @@ function show_ip(string $ip, string $ban_reason): string global $user; $u_reason = url_escape($ban_reason); $u_end = url_escape("+1 week"); - $ban = $user->can("ban_ip") ? ", Ban" : ""; - $ip = $user->can("view_ip") ? $ip.$ban : ""; + $ban = $user->can(Permissions::BAN_IP) ? ", Ban" : ""; + $ip = $user->can(Permissions::VIEW_IP) ? $ip.$ban : ""; return $ip; } diff --git a/ext/admin/main.php b/ext/admin/main.php index 212b07fb..423460c8 100644 --- a/ext/admin/main.php +++ b/ext/admin/main.php @@ -54,7 +54,7 @@ class AdminPage extends Extension global $page, $user; if ($event->page_matches("admin")) { - if (!$user->can("manage_admintools")) { + if (!$user->can(Permissions::MANAGE_ADMINTOOLS)) { $this->theme->display_permission_denied(); } else { if ($event->count_args() == 0) { @@ -70,7 +70,7 @@ class AdminPage extends Extension } if ($aae->redirect) { - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("admin")); } } @@ -108,10 +108,20 @@ class AdminPage extends Extension $this->theme->display_form(); } + public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) + { + global $user; + if($event->parent==="system") { + if ($user->can(Permissions::MANAGE_ADMINTOOLS)) { + $event->add_nav_link("admin", new Link('admin'), "Board Admin"); + } + } + } + public function onUserBlockBuilding(UserBlockBuildingEvent $event) { global $user; - if ($user->can("manage_admintools")) { + if ($user->can(Permissions::MANAGE_ADMINTOOLS)) { $event->add_link("Board Admin", make_link("admin")); } } @@ -137,6 +147,7 @@ class AdminPage extends Extension global $page; $query = $_POST['query']; $reason = @$_POST['reason']; + assert(strlen($query) > 1); $images = Image::find_images(0, 1000000, Tag::explode($query)); @@ -146,10 +157,10 @@ class AdminPage extends Extension if ($reason && class_exists("ImageBan")) { send_event(new AddImageHashBanEvent($image->hash, $reason)); } - send_event(new ImageDeletionEvent($image)); + send_event(new ImageDeletionEvent($image, true)); } - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("post/list")); return false; } @@ -201,14 +212,14 @@ class AdminPage extends Extension $database = $matches['dbname']; switch ($software) { - case 'mysql': + case DatabaseDriver::MYSQL: $cmd = "mysqldump -h$hostname -u$username -p$password $database"; break; - case 'pgsql': + case DatabaseDriver::PGSQL: putenv("PGPASSWORD=$password"); $cmd = "pg_dump -h $hostname -U $username $database"; break; - case 'sqlite': + case DatabaseDriver::SQLITE: $cmd = "sqlite3 $database .dump"; break; default: @@ -218,7 +229,7 @@ class AdminPage extends Extension //FIXME: .SQL dump is empty if cmd doesn't exist if ($cmd) { - $page->set_mode("data"); + $page->set_mode(PageMode::DATA); $page->set_type("application/x-unknown"); $page->set_filename('shimmie-'.date('Ymd').'.sql'); $page->set_data(shell_exec($cmd)); @@ -237,13 +248,13 @@ class AdminPage extends Extension $zip = new ZipArchive; if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) { foreach ($images as $img) { - $img_loc = warehouse_path("images", $img["hash"], false); + $img_loc = warehouse_path(Image::IMAGE_DIR, $img["hash"], false); $zip->addFile($img_loc, $img["hash"].".".$img["ext"]); } $zip->close(); } - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link($filename)); //TODO: Delete file after downloaded? return false; // we do want a redirect, but a manual one @@ -257,7 +268,7 @@ class AdminPage extends Extension //TODO: Update score_log (Having an optional ID column for score_log would be nice..) preg_match("#^(?P\w+)\:(?:user=(?P\w+)(?:;|$)|password=(?P\w*)(?:;|$)|host=(?P[\w\.\-]+)(?:;|$)|dbname=(?P[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches); - if ($matches['proto'] == "mysql") { + if ($matches['proto'] == DatabaseDriver::MYSQL) { $tables = $database->get_col("SELECT TABLE_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = :db @@ -280,9 +291,9 @@ class AdminPage extends Extension $i++; } $database->execute("ALTER TABLE images AUTO_INCREMENT=".(count($ids) + 1)); - } elseif ($matches['proto'] == "pgsql") { + } elseif ($matches['proto'] == DatabaseDriver::PGSQL) { //TODO: Make this work with PostgreSQL - } elseif ($matches['proto'] == "sqlite") { + } elseif ($matches['proto'] == DatabaseDriver::SQLITE) { //TODO: Make this work with SQLite } return true; diff --git a/ext/admin/theme.php b/ext/admin/theme.php index 3e60d224..1abe6542 100644 --- a/ext/admin/theme.php +++ b/ext/admin/theme.php @@ -45,7 +45,7 @@ class AdminPageTheme extends Themelet $html .= $this->button("Download all images", "download_all_images", false); } $html .= $this->button("Download database contents", "database_dump", false); - if ($database->get_driver_name() == "mysql") { + if ($database->get_driver_name() == DatabaseDriver::MYSQL) { $html .= $this->button("Reset image IDs", "reset_image_ids", true); } $page->add_block(new Block("Misc Admin Tools", $html)); @@ -55,16 +55,18 @@ class AdminPageTheme extends Themelet $html .= ""; $html .= "\n"; $page->add_block(new Block("Set Tag Case", $html)); + } public function dbq_html($terms) { - $h_terms = html_escape($terms); - $h_reason = ""; + if(ext_is_live("Trash")) { + $warning = "This delete method will bypass the trash
"; + } if (class_exists("ImageBan")) { $h_reason = ""; } - $html = make_form(make_link("admin/delete_by_query"), "POST") . " + $html = $warning.make_form(make_link("admin/delete_by_query"), "POST") . " $h_reason @@ -73,4 +75,6 @@ class AdminPageTheme extends Themelet "; return $html; } + + } diff --git a/ext/alias_editor/main.php b/ext/alias_editor/main.php index 9e7139cf..36edbfbb 100644 --- a/ext/alias_editor/main.php +++ b/ext/alias_editor/main.php @@ -36,12 +36,12 @@ class AliasEditor extends Extension if ($event->page_matches("alias")) { if ($event->get_arg(0) == "add") { - if ($user->can("manage_alias_list")) { + if ($user->can(Permissions::MANAGE_ALIAS_LIST)) { if (isset($_POST['oldtag']) && isset($_POST['newtag'])) { try { $aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']); send_event($aae); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("alias/list")); } catch (AddAliasException $ex) { $this->theme->display_error(500, "Error adding alias", $ex->getMessage()); @@ -49,12 +49,12 @@ class AliasEditor extends Extension } } } elseif ($event->get_arg(0) == "remove") { - if ($user->can("manage_alias_list")) { + if ($user->can(Permissions::MANAGE_ALIAS_LIST)) { if (isset($_POST['oldtag'])) { $database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", ["oldtag" => $_POST['oldtag']]); log_info("alias_editor", "Deleted alias for ".$_POST['oldtag'], "Deleted alias"); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("alias/list")); } } @@ -80,18 +80,18 @@ class AliasEditor extends Extension $this->theme->display_aliases($alias, $page_number + 1, $total_pages); } elseif ($event->get_arg(0) == "export") { - $page->set_mode("data"); + $page->set_mode(PageMode::DATA); $page->set_type("text/csv"); $page->set_filename("aliases.csv"); $page->set_data($this->get_alias_csv($database)); } elseif ($event->get_arg(0) == "import") { - if ($user->can("manage_alias_list")) { + if ($user->can(Permissions::MANAGE_ALIAS_LIST)) { if (count($_FILES) > 0) { $tmp = $_FILES['alias_file']['tmp_name']; $contents = file_get_contents($tmp); $this->add_alias_csv($database, $contents); log_info("alias_editor", "Imported aliases from file", "Imported aliases"); # FIXME: how many? - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("alias/list")); } else { $this->theme->display_error(400, "No File Specified", "You have to upload a file"); @@ -117,10 +117,17 @@ class AliasEditor extends Extension } } + public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) + { + if($event->parent=="tags") { + $event->add_nav_link("aliases", new Link('alias/list'), "Aliases", NavLink::is_active(["alias"])); + } + } + public function onUserBlockBuilding(UserBlockBuildingEvent $event) { global $user; - if ($user->can("manage_alias_list")) { + if ($user->can(Permissions::MANAGE_ALIAS_LIST)) { $event->add_link("Alias Editor", make_link("alias/list")); } } diff --git a/ext/alias_editor/theme.php b/ext/alias_editor/theme.php index ec12348e..732139d4 100644 --- a/ext/alias_editor/theme.php +++ b/ext/alias_editor/theme.php @@ -11,7 +11,7 @@ class AliasEditorTheme extends Themelet { global $page, $user; - $can_manage = $user->can("manage_alias_list"); + $can_manage = $user->can(Permissions::MANAGE_ALIAS_LIST); if ($can_manage) { $h_action = "Action"; $h_add = " diff --git a/ext/artists/main.php b/ext/artists/main.php index 7a3f3377..b1f6efcc 100644 --- a/ext/artists/main.php +++ b/ext/artists/main.php @@ -47,12 +47,23 @@ class Artists extends Extension public function onSearchTermParse(SearchTermParseEvent $event) { $matches = []; - if (preg_match("/^author[=|:](.*)$/i", $event->term, $matches)) { + if (preg_match("/^(author|artist)[=|:](.*)$/i", $event->term, $matches)) { $char = $matches[1]; $event->add_querylet(new Querylet("Author = :author_char", ["author_char"=>$char])); } } + public function onHelpPageBuilding(HelpPageBuildingEvent $event) + { + if($event->key===HelpPages::SEARCH) { + $block = new Block(); + $block->header = "Artist"; + $block->body = $this->theme->get_help_html(); + $event->add_block($block); + } + } + + public function onInitExt(InitExtEvent $event) { global $config, $database; @@ -172,7 +183,7 @@ class Artists extends Extension } case "new_artist": { - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/new")); break; } @@ -183,7 +194,7 @@ class Artists extends Extension if ($newArtistID == -1) { $this->theme->display_error(400, "Error", "Error when entering artist data."); } else { - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$newArtistID)); } } else { @@ -238,7 +249,7 @@ class Artists extends Extension case "edit_artist": { $artistID = $_POST['artist_id']; - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/edit/".$artistID)); break; } @@ -246,14 +257,14 @@ class Artists extends Extension { $artistID = int_escape($_POST['id']); $this->update_artist(); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } case "nuke_artist": { $artistID = $_POST['artist_id']; - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/nuke/".$artistID)); break; } @@ -261,7 +272,7 @@ class Artists extends Extension { $artistID = $event->get_arg(1); $this->delete_artist($artistID); // this will delete the artist, its alias, its urls and its members - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/list")); break; } @@ -291,7 +302,7 @@ class Artists extends Extension { $artistID = $_POST['artistID']; $this->add_alias(); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } @@ -300,7 +311,7 @@ class Artists extends Extension $aliasID = $event->get_arg(2); $artistID = $this->get_artistID_by_aliasID($aliasID); $this->delete_alias($aliasID); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } @@ -316,7 +327,7 @@ class Artists extends Extension $this->update_alias(); $aliasID = int_escape($_POST['aliasID']); $artistID = $this->get_artistID_by_aliasID($aliasID); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } @@ -332,7 +343,7 @@ class Artists extends Extension { $artistID = $_POST['artistID']; $this->add_urls(); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } @@ -341,7 +352,7 @@ class Artists extends Extension $urlID = $event->get_arg(2); $artistID = $this->get_artistID_by_urlID($urlID); $this->delete_url($urlID); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } @@ -357,7 +368,7 @@ class Artists extends Extension $this->update_url(); $urlID = int_escape($_POST['urlID']); $artistID = $this->get_artistID_by_urlID($urlID); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } @@ -372,7 +383,7 @@ class Artists extends Extension { $artistID = $_POST['artistID']; $this->add_members(); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } @@ -381,7 +392,7 @@ class Artists extends Extension $memberID = int_escape($event->get_arg(2)); $artistID = $this->get_artistID_by_memberID($memberID); $this->delete_member($memberID); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } @@ -397,7 +408,7 @@ class Artists extends Extension $this->update_member(); $memberID = int_escape($_POST['memberID']); $artistID = $this->get_artistID_by_memberID($memberID); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("artist/view/".$artistID)); break; } diff --git a/ext/artists/theme.php b/ext/artists/theme.php index aebd2757..1e5e5afb 100644 --- a/ext/artists/theme.php +++ b/ext/artists/theme.php @@ -545,4 +545,14 @@ class ArtistsTheme extends Themelet } return $html; } + + public function get_help_html() + { + return '

Search for images with a particular artist.

+
+
artist=leonardo
+

Returns images with the artist "leonardo".

+
+ '; + } } diff --git a/ext/autocomplete/main.php b/ext/autocomplete/main.php index eb71227b..187f45e5 100644 --- a/ext/autocomplete/main.php +++ b/ext/autocomplete/main.php @@ -21,7 +21,7 @@ class AutoComplete extends Extension return; } - $page->set_mode("data"); + $page->set_mode(PageMode::DATA); $page->set_type("application/json"); $s = strtolower($_GET["s"]); @@ -38,7 +38,9 @@ class AutoComplete extends Extension //$limit = 0; $cache_key = "autocomplete-$s"; $limitSQL = ""; - $SQLarr = ["search"=>"$s%"]; + $s = str_replace('_','\_', $s); + $s = str_replace('%','\%', $s); + $SQLarr = ["search"=>"$s%"]; #, "cat_search"=>"%:$s%"]; if (isset($_GET["limit"]) && $_GET["limit"] !== 0) { $limitSQL = "LIMIT :limit"; $SQLarr['limit'] = $_GET["limit"]; @@ -51,7 +53,8 @@ class AutoComplete extends Extension $database->scoreql_to_sql(" SELECT tag, count FROM tags - WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:search) + WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:search) + -- OR SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:cat_search) AND count > 0 ORDER BY count DESC $limitSQL"), diff --git a/ext/ban_words/main.php b/ext/ban_words/main.php index c668e514..5e0761b3 100644 --- a/ext/ban_words/main.php +++ b/ext/ban_words/main.php @@ -58,7 +58,7 @@ xanax public function onCommentPosting(CommentPostingEvent $event) { global $user; - if (!$user->can("bypass_comment_checks")) { + if (!$user->can(Permissions::BYPASS_COMMENT_CHECKS)) { $this->test_text($event->comment, new CommentPostingException("Comment contains banned terms")); } } diff --git a/ext/blocks/main.php b/ext/blocks/main.php index 86e0a1c5..197b5d9f 100644 --- a/ext/blocks/main.php +++ b/ext/blocks/main.php @@ -26,10 +26,20 @@ class Blocks extends Extension } } + public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) + { + global $user; + if($event->parent==="system") { + if ($user->can(Permissions::MANAGE_BLOCKS)) { + $event->add_nav_link("blocks", new Link('blocks/list'), "Blocks Editor"); + } + } + } + public function onUserBlockBuilding(UserBlockBuildingEvent $event) { global $user; - if ($user->can("manage_blocks")) { + if ($user->can(Permissions::MANAGE_BLOCKS)) { $event->add_link("Blocks Editor", make_link("blocks/list")); } } @@ -52,7 +62,7 @@ class Blocks extends Extension } } - if ($event->page_matches("blocks") && $user->can("manage_blocks")) { + if ($event->page_matches("blocks") && $user->can(Permissions::MANAGE_BLOCKS)) { if ($event->get_arg(0) == "add") { if ($user->check_auth_token()) { $database->execute(" @@ -61,7 +71,7 @@ class Blocks extends Extension ", [$_POST['pages'], $_POST['title'], $_POST['area'], (int)$_POST['priority'], $_POST['content']]); log_info("blocks", "Added Block #".($database->get_last_insert_id('blocks_id_seq'))." (".$_POST['title'].")"); $database->cache->delete("blocks"); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("blocks/list")); } } @@ -81,7 +91,7 @@ class Blocks extends Extension log_info("blocks", "Updated Block #".$_POST['id']." (".$_POST['title'].")"); } $database->cache->delete("blocks"); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("blocks/list")); } } elseif ($event->get_arg(0) == "list") { diff --git a/ext/blotter/main.php b/ext/blotter/main.php index 8f54576e..3e18b1a9 100644 --- a/ext/blotter/main.php +++ b/ext/blotter/main.php @@ -56,6 +56,17 @@ class Blotter extends Extension $event->panel->add_block($sb); } + public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) + { + global $user; + if($event->parent==="system") { + if ($user->is_admin()) { + $event->add_nav_link("blotter", new Link('blotter/editor'), "Blotter Editor"); + } + } + } + + public function onUserBlockBuilding(UserBlockBuildingEvent $event) { global $user; @@ -102,7 +113,7 @@ class Blotter extends Extension [$entry_text, $important] ); log_info("blotter", "Added Message: $entry_text"); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("blotter/editor")); } break; @@ -119,7 +130,7 @@ class Blotter extends Extension } $database->Execute("DELETE FROM blotter WHERE id=:id", ["id"=>$id]); log_info("blotter", "Removed Entry #$id"); - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("blotter/editor")); } break; diff --git a/ext/browser_search/main.php b/ext/browser_search/main.php index 10950000..301ca0db 100644 --- a/ext/browser_search/main.php +++ b/ext/browser_search/main.php @@ -27,14 +27,14 @@ class BrowserSearch extends Extension // Add in header code to let the browser know that the search plugin exists // We need to build the data for the header - $search_title = $config->get_string('title'); + $search_title = $config->get_string(SetupConfig::TITLE); $search_file_url = make_link('browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml'); $page->add_html_header(""); // The search.xml file that is generated on the fly if ($event->page_matches("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml")) { // First, we need to build all the variables we'll need - $search_title = $config->get_string('title'); + $search_title = $config->get_string(SetupConfig::TITLE); $search_form_url = make_link('post/list/{searchTerms}'); $suggenton_url = make_link('browser_search/')."{searchTerms}"; $icon_b64 = base64_encode(file_get_contents("ext/handle_static/static/favicon.ico")); @@ -54,7 +54,7 @@ class BrowserSearch extends Extension "; // And now to send it to the browser - $page->set_mode("data"); + $page->set_mode(PageMode::DATA); $page->set_type("text/xml"); $page->set_data($xml); } elseif ( @@ -85,7 +85,7 @@ class BrowserSearch extends Extension // And now for the final output $json_string = "[\"$tag_search\",[\"$json_tag_list\"],[],[]]"; - $page->set_mode("data"); + $page->set_mode(PageMode::DATA); $page->set_data($json_string); } } diff --git a/ext/bulk_actions/main.php b/ext/bulk_actions/main.php index 2c93de4e..0bfaf1da 100644 --- a/ext/bulk_actions/main.php +++ b/ext/bulk_actions/main.php @@ -14,22 +14,31 @@ class BulkActionBlockBuildingEvent extends Event /** @var array */ public $actions = []; - public function add_action(String $action, string $button_text, String $confirmation_message = "", String $block = "", int $position = 40) + public $search_terms = []; + + public function add_action(String $action, string $button_text, string $access_key = null, String $confirmation_message = "", String $block = "", int $position = 40) { if ($block == null) { $block = ""; } - array_push( - $this->actions, - [ + if(!empty($access_key)) { + assert(strlen($access_key)==1); + foreach ($this->actions as $existing) { + if($existing["access_key"]==$access_key) { + throw new SCoreException("Access key $access_key is already in use"); + } + } + } + + $this->actions[] =[ "block" => $block, + "access_key" => $access_key, "confirmation_message" => $confirmation_message, "action" => $action, "button_text" => $button_text, "position" => $position - ] - ); + ]; } } @@ -42,7 +51,7 @@ class BulkActionEvent extends Event /** @var PageRequestEvent */ public $page_request; - public function __construct(String $action, PageRequestEvent $pageRequestEvent, array $items) + public function __construct(String $action, PageRequestEvent $pageRequestEvent, Generator $items) { $this->action = $action; $this->page_request = $pageRequestEvent; @@ -58,6 +67,8 @@ class BulkActions extends Extension if ($user->is_logged_in()) { $babbe = new BulkActionBlockBuildingEvent(); + $babbe->search_terms = $event->search_terms; + send_event($babbe); if (sizeof($babbe->actions) == 0) { @@ -74,16 +85,23 @@ class BulkActions extends Extension { global $user; - if ($user->can("delete_image")) { - $event->add_action("bulk_delete", "Delete", "Delete selected images?", "", 10); + if ($user->can(Permissions::DELETE_IMAGE)) { + $event->add_action("bulk_delete", "(D)elete", "d", "Delete selected images?", $this->theme->render_ban_reason_input(), 10); } - if ($user->can("bulk_edit_image_tag")) { - $event->add_action("bulk_tag", "Tag", "", $this->theme->render_tag_input(), 10); + if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) { + + $event->add_action( + "bulk_tag", + "Tag", + "t", + "", + $this->theme->render_tag_input(), + 10); } - if ($user->can("bulk_edit_image_source")) { - $event->add_action("bulk_source", "Set Source", "", $this->theme->render_source_input(), 10); + if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) { + $event->add_action("bulk_source", "Set (S)ource", "s","", $this->theme->render_source_input(), 10); } } @@ -93,7 +111,7 @@ class BulkActions extends Extension switch ($event->action) { case "bulk_delete": - if ($user->can("delete_image")) { + if ($user->can(Permissions::DELETE_IMAGE)) { $i = $this->delete_items($event->items); flash_message("Deleted $i items"); } @@ -102,7 +120,7 @@ class BulkActions extends Extension if (!isset($_POST['bulk_tags'])) { return; } - if ($user->can("bulk_edit_image_tag")) { + if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) { $tags = $_POST['bulk_tags']; $replace = false; if (isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace'] == "true") { @@ -117,7 +135,7 @@ class BulkActions extends Extension if (!isset($_POST['bulk_source'])) { return; } - if ($user->can("bulk_edit_image_source")) { + if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) { $source = $_POST['bulk_source']; $i = $this->set_source($event->items, $source); flash_message("Set source for $i items"); @@ -129,49 +147,33 @@ class BulkActions extends Extension public function onPageRequest(PageRequestEvent $event) { global $page, $user; - if ($event->page_matches("bulk_action") && $user->is_admin()) { + if ($event->page_matches("bulk_action") && $user->can(Permissions::PERFORM_BULK_ACTIONS)) { if (!isset($_POST['bulk_action'])) { return; } $action = $_POST['bulk_action']; - $items = []; + $items = null; if (isset($_POST['bulk_selected_ids']) && $_POST['bulk_selected_ids'] != "") { $data = json_decode($_POST['bulk_selected_ids']); - if (is_array($data)) { - foreach ($data as $id) { - if (is_numeric($id)) { - array_push($items, int_escape($id)); - } - } + if (is_array($data)&&!empty($data)) { + $items = $this->yield_items($data); } } elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") { $query = $_POST['bulk_query']; if ($query != null && $query != "") { - $n = 0; - $tags = Tag::explode($query); - while (true) { - $results = Image::find_image_ids($n, 100, $tags); - if (count($results) == 0) { - break; - } - - reset($results); // rewind to first element in array. - $items = array_merge($items, $results); - $n += count($results); - } + $items = $this->yield_search_results($query); } } - if (sizeof($items) > 0) { - reset($items); // rewind to first element in array. + if (is_iterable($items)) { $newEvent = new BulkActionEvent($action, $event, $items); send_event($newEvent); } - $page->set_mode("redirect"); + $page->set_mode(PageMode::REDIRECT); if (!isset($_SERVER['HTTP_REFERER'])) { $_SERVER['HTTP_REFERER'] = make_link(); } @@ -179,31 +181,50 @@ class BulkActions extends Extension } } + private function yield_items(array $data): Generator + { + foreach ($data as $id) { + if (is_numeric($id)) { + $image = Image::by_id($id); + if($image!=null) { + yield $image; + } + } + } + } + + private function yield_search_results(string $query): Generator + { + $tags = Tag::explode($query); + return Image::find_images_iterable(0, null, $tags); + } + private function sort_blocks($a, $b) { return $a["position"] - $b["position"]; } - private function delete_items(array $items): int + private function delete_items(iterable $items): int { $total = 0; - foreach ($items as $id) { + foreach ($items as $image) { try { - $image = Image::by_id($id); - if ($image==null) { - continue; + if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) { + $reason = $_POST['bulk_ban_reason']; + if ($reason) { + send_event(new AddImageHashBanEvent($image->hash, $reason)); + } } - send_event(new ImageDeletionEvent($image)); $total++; } catch (Exception $e) { - flash_message("Error while removing $id: " . $e->getMessage(), "error"); + flash_message("Error while removing {$image->id}: " . $e->getMessage(), "error"); } } return $total; } - private function tag_items(array $items, string $tags, bool $replace): int + private function tag_items(iterable $items, string $tags, bool $replace): int { $tags = Tag::explode($tags); @@ -219,28 +240,21 @@ class BulkActions extends Extension $total = 0; if ($replace) { - foreach ($items as $id) { - $image = Image::by_id($id); - if ($image==null) { - continue; - } - + foreach ($items as $image) { send_event(new TagSetEvent($image, $tags)); $total++; } } else { - foreach ($items as $id) { - $image = Image::by_id($id); - if ($image==null) { - continue; - } + foreach ($items as $image) { + $img_tags = array_map("strtolower",$image->get_tag_array()); - $img_tags = []; if (!empty($neg_tag_array)) { - $img_tags = array_merge($pos_tag_array, $image->get_tag_array()); + $neg_tag_array = array_map("strtolower",$neg_tag_array); + + $img_tags = array_merge($pos_tag_array, $img_tags); $img_tags = array_diff($img_tags, $neg_tag_array); } else { - $img_tags = array_merge($tags, $image->get_tag_array()); + $img_tags = array_merge($tags, $img_tags); } send_event(new TagSetEvent($image, $img_tags)); $total++; @@ -250,23 +264,17 @@ class BulkActions extends Extension return $total; } - private function set_source(array $items, String $source): int + private function set_source(iterable $items, String $source): int { $total = 0; - foreach ($items as $id) { + foreach ($items as $image) { try { - $image = Image::by_id($id); - if ($image==null) { - continue; - } - send_event(new SourceSetEvent($image, $source)); $total++; } catch (Exception $e) { - flash_message("Error while setting source for $id: " . $e->getMessage(), "error"); + flash_message("Error while setting source for {$image->id}: " . $e->getMessage(), "error"); } } - return $total; } } diff --git a/ext/bulk_actions/script.js b/ext/bulk_actions/script.js index b5e73f8b..288449d9 100644 --- a/ext/bulk_actions/script.js +++ b/ext/bulk_actions/script.js @@ -37,7 +37,7 @@ function validate_selections(form, confirmationMessage) { function activate_bulk_selector () { set_selected_items([]); if(!bulk_selector_initialized) { - $("a.shm-thumb").each( + $(".shm-thumb").each( function (index, block) { add_selector_button($(block)); } @@ -67,11 +67,11 @@ function get_selected_items() { } function set_selected_items(items) { - $("a.shm-thumb").removeClass('selected'); + $(".shm-thumb").removeClass('selected'); $(items).each( function(index,item) { - $('a.shm-thumb[data-post-id="' + item + '"]').addClass('selected'); + $('.shm-thumb[data-post-id="' + item + '"]').addClass('selected'); } ); @@ -109,7 +109,7 @@ function toggle_selection( id ) { function select_all() { var items = []; - $("a.shm-thumb").each( + $(".shm-thumb").each( function ( index, block ) { block = $(block); var id = block.data("post-id"); @@ -122,7 +122,7 @@ function select_all() { function select_invert() { var currentItems = get_selected_items(); var items = []; - $("a.shm-thumb").each( + $(".shm-thumb").each( function ( index, block ) { block = $(block); var id = block.data("post-id"); @@ -141,7 +141,7 @@ function select_none() { function select_range(start, end) { var data = get_selected_items(); var selecting = false; - $("a.shm-thumb").each( + $(".shm-thumb").each( function ( index, block ) { block = $(block); var id = block.data("post-id"); diff --git a/ext/bulk_actions/theme.php b/ext/bulk_actions/theme.php index 538c74df..ed3c8a1e 100644 --- a/ext/bulk_actions/theme.php +++ b/ext/bulk_actions/theme.php @@ -2,14 +2,14 @@ class BulkActionsTheme extends Themelet { - public function display_selector(Page $page, $actions, $query) + public function display_selector(Page $page, array $actions, string $query) { global $user; $body = " - + "; @@ -69,87 +56,13 @@ class Layout } } - $custom_sublinks = "
"; - // hack - $username = url_escape($user->name); - // hack - $qp = explode("/", ltrim(_get_query(), "/")); - $cs = ""; - - // php sucks - switch ($qp[0]) { - default: - $cs = $user_block_html; - break; - case "": - # FIXME: this assumes that the front page is - # post/list; in 99% of case it will either be - # post/list or home, and in the latter case - # the subnav links aren't shown, but it would - # be nice to be correct - case "post": - if (class_exists("NumericScore")) { - $cs .= "Popular by Day/Month/Year "; - } - $cs .= "All"; - if (class_exists("Favorites")) { - $cs .= "My Favorites"; - } - if (class_exists("RSS_Images")) { - $cs .= "Feed"; - } - if (class_exists("Random_Image")) { - $cs .= "Random Image"; - } - if (class_exists("Wiki")) { - $cs .= "Help"; - } else { - $cs .= "Help"; - } - break; - case "comment": - $cs .= "All"; - $cs .= "Feed"; - $cs .= "Help"; - break; - case "pool": - $cs .= "List"; - $cs .= "Create"; - $cs .= "Changes"; - $cs .= "Help"; - break; - case "wiki": - $cs .= "Index"; - $cs .= "Rules"; - $cs .= "Help"; - break; - case "tags": - case "alias": - $cs .= "Map"; - $cs .= "Alphabetic"; - $cs .= "Popularity"; - $cs .= "Categories"; - $cs .= "Aliases"; - $cs .= "Help"; - break; - case "upload": - if (class_exists("Wiki")) { - $cs .= "Guidelines"; - } - break; - case "random": - $cs .= "Shuffle"; - $cs .= "Download"; - break; - case "featured": - $cs .= "Download"; - break; - } - - if ($cs == "") { - $custom_sublinks = ""; - } else { - $custom_sublinks .= "$cs
"; + $custom_sublinks = ""; + if(!empty($sub_links)) { + $custom_sublinks = "
"; + foreach ($sub_links as $nav_link) { + $custom_sublinks .= $this->navlinks($nav_link->link, $nav_link->description, $nav_link->active); + } + $custom_sublinks .= "
"; } $debug = get_debug_info(); @@ -240,31 +153,13 @@ EOD; /** * #param string[] $pages_matched */ - public function navlinks(string $link, string $desc, array $pages_matched): ?string + public function navlinks(Link $link, string $desc, bool $active): ?string { - /** - * Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.) - */ $html = null; - $url = ltrim(_get_query(), "/"); - - $re1='.*?'; - $re2='((?:[a-z][a-z_]+))'; - - if (preg_match_all("/".$re1.$re2."/is", $url, $matches)) { - $url=$matches[1][0]; - } - - $count_pages_matched = count($pages_matched); - - for ($i=0; $i < $count_pages_matched; $i++) { - if ($url == $pages_matched[$i]) { - $html = "{$desc}"; - } - } - - if (is_null($html)) { - $html = "{$desc}"; + if ($active) { + $html = "{$desc}"; + } else { + $html = "{$desc}"; } return $html; diff --git a/themes/lite/style.css b/themes/lite/style.css index 47bfeaae..2b7d61f2 100644 --- a/themes/lite/style.css +++ b/themes/lite/style.css @@ -140,7 +140,7 @@ TABLE.zebra TR TD {border-bottom: 1px solid #C3D2E0;} TABLE.zebra TR:nth-child(odd) {background: #CEDFF0;} TABLE.zebra TR:nth-child(even) {background: #F0F7FF;} -INPUT, TEXTAREA { +INPUT, TEXTAREA, button { -moz-border-radius:4px; -webkit-border-radius:4px; border:1px solid #C8D1DB; @@ -149,7 +149,7 @@ INPUT, TEXTAREA { padding:2px; } -INPUT:hover, TEXTAREA:hover { +INPUT:hover, button:hover, TEXTAREA:hover { background-color:#FFFFFF; text-decoration:none; } @@ -196,7 +196,7 @@ NAV TABLE { NAV TD { vertical-align: middle; } -NAV INPUT { +NAV INPUT, nav button { width: 95%; padding: 0px; } diff --git a/themes/lite/view.theme.php b/themes/lite/view.theme.php index b3ae0046..f9ce414f 100644 --- a/themes/lite/view.theme.php +++ b/themes/lite/view.theme.php @@ -5,7 +5,6 @@ class CustomViewImageTheme extends ViewImageTheme public function display_page(Image $image, $editor_parts) { global $page; - $page->set_title("Image {$image->id}: ".html_escape($image->get_tag_list())); $page->set_heading(html_escape($image->get_tag_list())); $page->add_block(new Block("Navigation", $this->build_navigation($image), "left", 0)); $page->add_block(new Block("Statistics", $this->build_stats($image), "left", 15)); @@ -18,11 +17,12 @@ class CustomViewImageTheme extends ViewImageTheme $h_owner = html_escape($image->get_owner()->name); $h_ownerlink = "$h_owner"; $h_ip = html_escape($image->owner_ip); + $h_type = html_escape($image->get_mime_type()); $h_date = autodate($image->posted); $h_filesize = to_shorthand_int($image->filesize); global $user; - if ($user->can("view_ip")) { + if ($user->can(Permissions::VIEW_IP)) { $h_ownerlink .= " ($h_ip)"; } @@ -31,7 +31,13 @@ class CustomViewImageTheme extends ViewImageTheme
Posted: $h_date by $h_ownerlink
Size: {$image->width}x{$image->height}
Filesize: $h_filesize +
Type: ".$h_type." "; + if($image->length!=null) { + $h_length = format_milliseconds($image->length); + $html .= "
Length: $h_length"; + } + if (!is_null($image->source)) { $h_source = html_escape($image->source); diff --git a/themes/material/home.theme.php b/themes/material/home.theme.php index 68b700e1..ada70b17 100644 --- a/themes/material/home.theme.php +++ b/themes/material/home.theme.php @@ -4,7 +4,7 @@ class CustomHomeTheme extends HomeTheme { public function display_page(Page $page, $sitename, $base_href, $theme_name, $body) { - $page->set_mode("data"); + $page->set_mode(PageMode::DATA); $page->add_auto_html_headers(); $hh = $page->get_all_html_headers(); $page->set_data( diff --git a/themes/material/layout.class.php b/themes/material/layout.class.php index 4c923456..ada06030 100644 --- a/themes/material/layout.class.php +++ b/themes/material/layout.class.php @@ -11,10 +11,10 @@ class Layout { global $config; - $theme_name = $config->get_string('theme', 'material'); - $site_name = $config->get_string('title'); + $theme_name = $config->get_string(SetupConfig::THEME, 'material'); + $site_name = $config->get_string(SetupConfig::TITLE); $data_href = get_base_href(); - $main_page = $config->get_string('main_page'); + $main_page = $config->get_string(SetupConfig::MAIN_PAGE); $contact_link = contact_link(); $site_link = make_link(); $header_html = $page->get_all_html_headers(); diff --git a/themes/material/view.theme.php b/themes/material/view.theme.php index eafdf3f4..601faf94 100644 --- a/themes/material/view.theme.php +++ b/themes/material/view.theme.php @@ -8,7 +8,6 @@ class CustomViewImageTheme extends ViewImageTheme public function display_page(Image $image, $editor_parts) { global $page; - $page->set_title("Image {$image->id}: ".html_escape($image->get_tag_list())); $page->set_heading(html_escape($image->get_tag_list())); $page->add_block(new Block(null, $this->build_pin($image), "subtoolbar", 0)); $page->add_block(new Block(null, $this->build_info($image, $editor_parts), "left", 20)); @@ -57,8 +56,8 @@ class CustomViewImageTheme extends ViewImageTheme $html .= $part; } if ( - (!$image->is_locked() || $user->can("edit_image_lock")) && - $user->can("edit_image_tag") + (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) && + $user->can(Permissions::EDIT_IMAGE_TAG) ) { $html .= " diff --git a/themes/warm/layout.class.php b/themes/warm/layout.class.php index 20259570..1d3017fb 100644 --- a/themes/warm/layout.class.php +++ b/themes/warm/layout.class.php @@ -11,10 +11,10 @@ class Layout { global $config; - //$theme_name = $config->get_string('theme', 'default'); - $site_name = $config->get_string('title'); + //$theme_name = $config->get_string(SetupConfig::THEME, 'default'); + $site_name = $config->get_string(SetupConfig::TITLE); $data_href = get_base_href(); - $main_page = $config->get_string('main_page'); + $main_page = $config->get_string(SetupConfig::MAIN_PAGE); $contact_link = contact_link(); $header_html = $page->get_all_html_headers();