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) : ""; $err_msg = $errors ? "
", $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 Search for images with a particular artist. Returns images with the artist "leonardo".
";
+ }
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 'artist=leonardo
+