From 5897bfdb7b0ba7e0b54a48a83cac8233e6082246 Mon Sep 17 00:00:00 2001 From: Shish Date: Wed, 13 Dec 2023 23:51:51 +0000 Subject: [PATCH 001/154] Basic devcontainer setup Getting xdebug working on osx is a pain, devcontainers are less pain... --- .devcontainer/devcontainer.json | 40 +++++++++++++++++++++++++++++++++ .gitignore | 1 - Dockerfile | 5 +++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..4a0f654d70 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,40 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile +{ + "name": "Shimmie Dockerfile", + "build": { + "context": "..", + "dockerfile": "../Dockerfile", + "target": "devcontainer" + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Uncomment the next line to run commands after the container is created. + // "postCreateCommand": "apt update && apt install -y composer php8.2-xdebug git", + + "customizations": { + "vscode": { + "extensions": [ + "recca0120.vscode-phpunit", + "ryanluker.vscode-coverage-gutters" + ], + "settings": { + "phpunit.args": [ + "--configuration", "${workspaceFolder}/tests/phpunit.xml"//, + //"--coverage-clover", "data/coverage.clover" + ], + "coverage-gutters.coverageFileNames": [ + "data/coverage.clover" + ] + } + } + } + + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "devcontainer" +} diff --git a/.gitignore b/.gitignore index 4f3b64dcd5..2edf41700a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ thumbs *.phar *.sqlite *.cache -.devcontainer trace.json #Composer diff --git a/Dockerfile b/Dockerfile index 75508f592e..7477642cc3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ RUN apt update && apt upgrade -y && apt install -y \ # Composer has 100MB of dependencies, and we only need that during build and test FROM base AS composer RUN apt update && apt upgrade -y && apt install -y composer php${PHP_VERSION}-xdebug && rm -rf /var/lib/apt/lists/* +ENV XDEBUG_MODE=coverage # "Build" shimmie (composer install - done in its own stage so that we don't # need to include all the composer fluff in the final image) @@ -36,6 +37,10 @@ RUN [ $RUN_TESTS = false ] || (\ echo '=== Coverage ===' && XDEBUG_MODE=coverage ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \ echo '=== Cleaning ===' && rm -rf data) +# Devcontainer target +FROM composer AS devcontainer +RUN apt update && apt upgrade -y && apt install -y git && rm -rf /var/lib/apt/lists/* + # Actually run shimmie FROM base EXPOSE 8000 From e114057dfead926086c4af6fa12b2b0882fa0269 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 00:14:24 +0000 Subject: [PATCH 002/154] PHPUnit 10 (and other bumps) --- composer.json | 8 +- composer.lock | 801 +++++++++--------- .../{basepage.test.php => BasePageTest.php} | 0 core/tests/{block.test.php => BlockTest.php} | 0 core/tests/{init.test.php => InitTest.php} | 2 +- .../{polyfills.test.php => PolyfillsTest.php} | 0 core/tests/{tag.test.php => TagTest.php} | 0 core/tests/{urls.test.php => UrlsTest.php} | 0 core/tests/{util.test.php => UtilTest.php} | 0 ext/image_hash_ban/test.php | 2 +- ext/index/test.php | 30 +- ext/pools/test.php | 20 +- ext/relationships/test.php | 30 +- tests/bootstrap.php | 2 +- tests/phpunit.xml | 19 +- 15 files changed, 441 insertions(+), 473 deletions(-) rename core/tests/{basepage.test.php => BasePageTest.php} (100%) rename core/tests/{block.test.php => BlockTest.php} (100%) rename core/tests/{init.test.php => InitTest.php} (91%) rename core/tests/{polyfills.test.php => PolyfillsTest.php} (100%) rename core/tests/{tag.test.php => TagTest.php} (100%) rename core/tests/{urls.test.php => UrlsTest.php} (100%) rename core/tests/{util.test.php => UtilTest.php} (100%) diff --git a/composer.json b/composer.json index 2732020a78..4d12054dc1 100644 --- a/composer.json +++ b/composer.json @@ -60,10 +60,10 @@ }, "require-dev" : { - "phpunit/phpunit" : "^9.0", - "friendsofphp/php-cs-fixer" : "^3.12", - "scrutinizer/ocular": "dev-master", - "phpstan/phpstan": "1.10.x-dev" + "phpunit/phpunit" : "10.5.3", + "friendsofphp/php-cs-fixer" : "3.41.1", + "scrutinizer/ocular": "1.9", + "phpstan/phpstan": "1.10.50" }, "suggest": { "ext-memcache": "memcache caching", diff --git a/composer.lock b/composer.lock index b288870e4b..20ff5649fe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "88e93ac9426438e6dc11301eb9faeed1", + "content-hash": "422b41bfec446ff1062a14df333a8bbd", "packages": [ { "name": "axy/backtrace", @@ -303,20 +303,20 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.16.0", + "version": "v4.17.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", "shasum": "" }, "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", @@ -358,9 +358,9 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" }, - "time": "2022-09-18T07:06:19+00:00" + "time": "2023-11-17T15:01:25+00:00" }, { "name": "flexihash/flexihash", @@ -1121,16 +1121,16 @@ }, { "name": "webonyx/graphql-php", - "version": "v15.7.0", + "version": "v15.8.1", "source": { "type": "git", "url": "https://github.com/webonyx/graphql-php.git", - "reference": "44ff70977ee020c0b24bfdfaf947be56943de505" + "reference": "575ac95f13adfb38219a748572355385c101fdf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/44ff70977ee020c0b24bfdfaf947be56943de505", - "reference": "44ff70977ee020c0b24bfdfaf947be56943de505", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/575ac95f13adfb38219a748572355385c101fdf7", + "reference": "575ac95f13adfb38219a748572355385c101fdf7", "shasum": "" }, "require": { @@ -1148,16 +1148,16 @@ "nyholm/psr7": "^1.5", "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "1.10.37", - "phpstan/phpstan-phpunit": "1.3.14", - "phpstan/phpstan-strict-rules": "1.5.1", + "phpstan/phpstan": "1.10.47", + "phpstan/phpstan-phpunit": "1.3.15", + "phpstan/phpstan-strict-rules": "1.5.2", "phpunit/phpunit": "^9.5 || ^10", "psr/http-message": "^1 || ^2", "react/http": "^1.6", "react/promise": "^2.9", "rector/rector": "^0.18", "symfony/polyfill-php81": "^1.23", - "symfony/var-exporter": "^5 || ^6", + "symfony/var-exporter": "^5 || ^6 || ^7", "thecodingmachine/safe": "^1.3 || ^2" }, "suggest": { @@ -1183,7 +1183,7 @@ ], "support": { "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v15.7.0" + "source": "https://github.com/webonyx/graphql-php/tree/v15.8.1" }, "funding": [ { @@ -1191,7 +1191,7 @@ "type": "open_collective" } ], - "time": "2023-10-04T09:10:34+00:00" + "time": "2023-12-05T17:23:35+00:00" } ], "packages-dev": [ @@ -1498,25 +1498,25 @@ "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "f49f6a836a816609c853718ba2ea422dc18a8e4a" + "reference": "6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f49f6a836a816609c853718ba2ea422dc18a8e4a", - "reference": "f49f6a836a816609c853718ba2ea422dc18a8e4a", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e", + "reference": "6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^12", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.9.4", "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^10.1.0", + "phpunit/phpunit": "^10.5", "vimeo/psalm": "^5.4" }, "default-branch": true, @@ -1561,7 +1561,7 @@ "type": "tidelift" } ], - "time": "2023-08-24T20:23:35+00:00" + "time": "2023-12-09T14:19:21+00:00" }, { "name": "doctrine/lexer", @@ -1642,50 +1642,48 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.38.0", + "version": "v3.41.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "7e6070026e76aa09d77a47519625c86593fb8e31" + "reference": "8b6ae8dcbaf23f09680643ab832a4a3a260265f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7e6070026e76aa09d77a47519625c86593fb8e31", - "reference": "7e6070026e76aa09d77a47519625c86593fb8e31", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/8b6ae8dcbaf23f09680643ab832a4a3a260265f6", + "reference": "8b6ae8dcbaf23f09680643ab832a4a3a260265f6", "shasum": "" }, "require": { - "composer/semver": "^3.3", + "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.3", "ext-json": "*", "ext-tokenizer": "*", "php": "^7.4 || ^8.0", "sebastian/diff": "^4.0 || ^5.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/options-resolver": "^5.4 || ^6.0", - "symfony/polyfill-mbstring": "^1.27", - "symfony/polyfill-php80": "^1.27", - "symfony/polyfill-php81": "^1.27", - "symfony/process": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { "facile-it/paraunit": "^1.3 || ^2.0", "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^2.0", + "keradus/cli-executor": "^2.1", "mikey179/vfsstream": "^1.6.11", - "php-coveralls/php-coveralls": "^2.5.3", + "php-coveralls/php-coveralls": "^2.7", "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.16", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "symfony/phpunit-bridge": "^6.2.3", - "symfony/yaml": "^5.4 || ^6.0" + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", + "phpunit/phpunit": "^9.6", + "symfony/phpunit-bridge": "^6.3.8 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -1723,7 +1721,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.38.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.41.1" }, "funding": [ { @@ -1731,7 +1729,7 @@ "type": "github" } ], - "time": "2023-11-07T08:44:54+00:00" + "time": "2023-12-10T19:59:27+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1739,12 +1737,12 @@ "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d95d9ab74822c2ca06b31477cd6775a4a299b8e8" + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d95d9ab74822c2ca06b31477cd6775a4a299b8e8", - "reference": "d95d9ab74822c2ca06b31477cd6775a4a299b8e8", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { @@ -1759,11 +1757,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1841,7 +1839,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8" + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, "funding": [ { @@ -1857,7 +1855,7 @@ "type": "tidelift" } ], - "time": "2023-09-11T09:12:01+00:00" + "time": "2023-12-03T20:35:24+00:00" }, { "name": "guzzlehttp/promises", @@ -1865,20 +1863,20 @@ "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "71b12f952d8be69a239989db2fe08314a5189e3b" + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/71b12f952d8be69a239989db2fe08314a5189e3b", - "reference": "71b12f952d8be69a239989db2fe08314a5189e3b", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "default-branch": true, "type": "library", @@ -1925,7 +1923,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0" + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, "funding": [ { @@ -1941,7 +1939,7 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:15:36+00:00" + "time": "2023-12-03T20:19:20+00:00" }, { "name": "guzzlehttp/psr7", @@ -1949,12 +1947,12 @@ "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "38ef514a6c21335f29d9be64b097d2582ecbf8e4" + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/38ef514a6c21335f29d9be64b097d2582ecbf8e4", - "reference": "38ef514a6c21335f29d9be64b097d2582ecbf8e4", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { @@ -1968,9 +1966,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2042,7 +2040,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6" + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { @@ -2058,7 +2056,7 @@ "type": "tidelift" } ], - "time": "2023-09-10T16:11:14+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { "name": "jms/metadata", @@ -2131,12 +2129,12 @@ "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "f24027583f8ad3c1789bff412b039013132316ac" + "reference": "c586bf6061deb4109330c2c90f3a315b7e2dbc4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/f24027583f8ad3c1789bff412b039013132316ac", - "reference": "f24027583f8ad3c1789bff412b039013132316ac", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c586bf6061deb4109330c2c90f3a315b7e2dbc4a", + "reference": "c586bf6061deb4109330c2c90f3a315b7e2dbc4a", "shasum": "" }, "require": { @@ -2149,7 +2147,7 @@ }, "require-dev": { "doctrine/coding-standard": "^12.0", - "doctrine/orm": "^2.14", + "doctrine/orm": "^2.14 || ^3.0", "doctrine/persistence": "^2.5.2 || ^3.0", "doctrine/phpcr-odm": "^1.5.2 || ^2.0", "ext-pdo_sqlite": "*", @@ -2212,7 +2210,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/serializer/issues", - "source": "https://github.com/schmittjoh/serializer/tree/master" + "source": "https://github.com/schmittjoh/serializer/tree/3.29.0" }, "funding": [ { @@ -2220,7 +2218,7 @@ "type": "github" } ], - "time": "2023-10-29T15:36:01+00:00" + "time": "2023-12-09T11:57:15+00:00" }, { "name": "myclabs/deep-copy", @@ -2289,12 +2287,12 @@ "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "402b6cf3452c21c58aa11d9549cee6205d14e347" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/402b6cf3452c21c58aa11d9549cee6205d14e347", - "reference": "402b6cf3452c21c58aa11d9549cee6205d14e347", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -2337,7 +2335,7 @@ "issues": "https://github.com/nikic/PHP-Parser/issues", "source": "https://github.com/nikic/PHP-Parser/tree/4.x" }, - "time": "2023-11-01T20:31:02+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "phar-io/manifest", @@ -2464,12 +2462,12 @@ "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" + "reference": "416ca2ac2a84555b785a98002d613fe13d1d1c2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", - "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/416ca2ac2a84555b785a98002d613fe13d1d1c2f", + "reference": "416ca2ac2a84555b785a98002d613fe13d1d1c2f", "shasum": "" }, "require": { @@ -2477,14 +2475,14 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "default-branch": true, "type": "library", "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "1.9-dev" @@ -2520,7 +2518,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" + "source": "https://github.com/schmittjoh/php-option/tree/master" }, "funding": [ { @@ -2532,20 +2530,20 @@ "type": "tidelift" } ], - "time": "2023-02-25T19:38:58+00:00" + "time": "2023-11-12T22:52:20+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.2", + "version": "1.24.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "bcad8d995980440892759db0c32acae7c8e79442" + "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", - "reference": "bcad8d995980440892759db0c32acae7c8e79442", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496", + "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496", "shasum": "" }, "require": { @@ -2577,22 +2575,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4" }, - "time": "2023-09-26T12:28:12+00:00" + "time": "2023-11-26T18:29:22+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.x-dev", + "version": "1.10.50", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "2fa2aa0de39a1ac2d44d3a0a03f7586720882635" + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2fa2aa0de39a1ac2d44d3a0a03f7586720882635", - "reference": "2fa2aa0de39a1ac2d44d3a0a03f7586720882635", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", "shasum": "" }, "require": { @@ -2641,20 +2639,20 @@ "type": "tidelift" } ], - "time": "2023-11-11T20:39:57+00:00" + "time": "2023-12-13T10:59:42+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "00bc23209e885ae1aab33c9a2303b0eb8ebd5c28" + "reference": "599109c8ca6bae97b23482d557d2874c25a65e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/00bc23209e885ae1aab33c9a2303b0eb8ebd5c28", - "reference": "00bc23209e885ae1aab33c9a2303b0eb8ebd5c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/599109c8ca6bae97b23482d557d2874c25a65e59", + "reference": "599109c8ca6bae97b23482d557d2874c25a65e59", "shasum": "" }, "require": { @@ -2662,27 +2660,28 @@ "ext-libxml": "*", "ext-xmlwriter": "*", "nikic/php-parser": "^4.15", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-text-template": "^3.0", + "sebastian/code-unit-reverse-lookup": "^3.0", + "sebastian/complexity": "^3.0", + "sebastian/environment": "^6.0", + "sebastian/lines-of-code": "^2.0", + "sebastian/version": "^4.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "10.1-dev" } }, "autoload": { @@ -2711,7 +2710,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.10" }, "funding": [ { @@ -2719,32 +2718,32 @@ "type": "github" } ], - "time": "2023-11-09T06:48:38+00:00" + "time": "2023-12-11T06:28:43+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.x-dev", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "38b24367e1b340aa78b96d7cab042942d917bb84" + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/38b24367e1b340aa78b96d7cab042942d917bb84", - "reference": "38b24367e1b340aa78b96d7cab042942d917bb84", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2771,7 +2770,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" }, "funding": [ { @@ -2779,36 +2779,37 @@ "type": "github" } ], - "time": "2022-02-11T16:23:04+00:00" + "time": "2023-08-31T06:24:48+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "a2fd9d53892abd0b30796cf6e64e6c1ce9d3d82f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/a2fd9d53892abd0b30796cf6e64e6c1ce9d3d82f", + "reference": "a2fd9d53892abd0b30796cf6e64e6c1ce9d3d82f", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-pcntl": "*" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2834,7 +2835,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/main" }, "funding": [ { @@ -2842,32 +2844,33 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2023-12-09T12:53:14+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "36ce468c414d5fb1e0667d991b45bee6cac28c8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/36ce468c414d5fb1e0667d991b45bee6cac28c8b", + "reference": "36ce468c414d5fb1e0667d991b45bee6cac28c8b", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2893,7 +2896,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/main" }, "funding": [ { @@ -2901,32 +2905,33 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2023-12-09T12:53:24+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "d118c2e2a37f6b0d5f4cd51d16c6203ada6eff48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/d118c2e2a37f6b0d5f4cd51d16c6203ada6eff48", + "reference": "d118c2e2a37f6b0d5f4cd51d16c6203ada6eff48", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -2952,7 +2957,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/main" }, "funding": [ { @@ -2960,24 +2966,23 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2023-12-09T12:53:34+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.x-dev", + "version": "10.5.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e2444b311b9bcca7c8b5294dc0c27a3fec0832a" + "reference": "6fce887c71076a73f32fd3e0774a6833fc5c7f19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e2444b311b9bcca7c8b5294dc0c27a3fec0832a", - "reference": "3e2444b311b9bcca7c8b5294dc0c27a3fec0832a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6fce887c71076a73f32fd3e0774a6833fc5c7f19", + "reference": "6fce887c71076a73f32fd3e0774a6833fc5c7f19", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -2987,27 +2992,26 @@ "myclabs/deep-copy": "^1.10.1", "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.5", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-invoker": "^4.0", + "phpunit/php-text-template": "^3.0", + "phpunit/php-timer": "^6.0", + "sebastian/cli-parser": "^2.0", + "sebastian/code-unit": "^2.0", + "sebastian/comparator": "^5.0", + "sebastian/diff": "^5.0", + "sebastian/environment": "^6.0", + "sebastian/exporter": "^5.1", + "sebastian/global-state": "^6.0.1", + "sebastian/object-enumerator": "^5.0", + "sebastian/recursion-context": "^5.0", + "sebastian/type": "^4.0", + "sebastian/version": "^4.0" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -3015,7 +3019,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "10.5-dev" } }, "autoload": { @@ -3047,7 +3051,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.3" }, "funding": [ { @@ -3063,7 +3067,7 @@ "type": "tidelift" } ], - "time": "2023-11-09T06:47:38+00:00" + "time": "2023-12-13T07:25:23+00:00" }, { "name": "psr/cache", @@ -3485,7 +3489,7 @@ }, { "name": "scrutinizer/ocular", - "version": "dev-master", + "version": "1.9", "source": { "type": "git", "url": "https://github.com/scrutinizer-ci/ocular.git", @@ -3509,7 +3513,6 @@ "phpunit/phpunit": "^9.0.0", "symfony/filesystem": "~2.0|~3.0|~4.0|~5.0|^6.0" }, - "default-branch": true, "bin": [ "bin/ocular" ], @@ -3531,28 +3534,29 @@ }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "333fb1f9faea50aea580da4c9150e4f862d95335" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/333fb1f9faea50aea580da4c9150e4f862d95335", + "reference": "333fb1f9faea50aea580da4c9150e4f862d95335", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -3575,7 +3579,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/main" }, "funding": [ { @@ -3583,32 +3588,33 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2023-12-09T12:50:59+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "fc85d823fe82d442f09c28148f01f2de1406bfd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/fc85d823fe82d442f09c28148f01f2de1406bfd5", + "reference": "fc85d823fe82d442f09c28148f01f2de1406bfd5", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -3631,7 +3637,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/main" }, "funding": [ { @@ -3639,32 +3646,33 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2023-12-09T12:51:12+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "a5d4b197dabd362421d775b6bf18b8a6b3660739" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/a5d4b197dabd362421d775b6bf18b8a6b3660739", + "reference": "a5d4b197dabd362421d775b6bf18b8a6b3660739", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3686,7 +3694,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/main" }, "funding": [ { @@ -3694,34 +3703,37 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2023-12-09T12:51:22+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "b247957a1c8dc81a671770f74b479c0a78a818f1" + "reference": "53353f1eef05cf26c4e29cb48d5bd4c830df3725" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b247957a1c8dc81a671770f74b479c0a78a818f1", - "reference": "b247957a1c8dc81a671770f74b479c0a78a818f1", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/53353f1eef05cf26c4e29cb48d5bd4c830df3725", + "reference": "53353f1eef05cf26c4e29cb48d5bd4c830df3725", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.4" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -3760,7 +3772,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/main" }, "funding": [ { @@ -3768,33 +3781,34 @@ "type": "github" } ], - "time": "2022-09-14T12:46:14+00:00" + "time": "2023-12-09T12:51:33+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "318aa5a6acf7728c9af26d03cc6af25693b27cf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/318aa5a6acf7728c9af26d03cc6af25693b27cf5", + "reference": "318aa5a6acf7728c9af26d03cc6af25693b27cf5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^4.10", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -3817,7 +3831,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/main" }, "funding": [ { @@ -3825,33 +3840,34 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-09T12:51:43+00:00" }, { "name": "sebastian/diff", - "version": "4.0.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "76dcc64d78011058dc262086ae96093098dbfbc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/76dcc64d78011058dc262086ae96093098dbfbc6", + "reference": "76dcc64d78011058dc262086ae96093098dbfbc6", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^10.0", "symfony/process": "^4.2 || ^5" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -3883,7 +3899,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/main" }, "funding": [ { @@ -3891,35 +3908,36 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2023-12-09T12:51:53+00:00" }, { "name": "sebastian/environment", - "version": "5.1.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "b720f6f2c69d57d89b52b966026ed4271d29462e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/b720f6f2c69d57d89b52b966026ed4271d29462e", + "reference": "b720f6f2c69d57d89b52b966026ed4271d29462e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-posix": "*" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -3938,7 +3956,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -3946,7 +3964,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/main" }, "funding": [ { @@ -3954,34 +3973,35 @@ "type": "github" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2023-12-09T12:52:03+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "032cf1796b94e920f6059f3f66ef3dec00d89166" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/032cf1796b94e920f6059f3f66ef3dec00d89166", + "reference": "032cf1796b94e920f6059f3f66ef3dec00d89166", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -4023,7 +4043,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/main" }, "funding": [ { @@ -4031,38 +4052,36 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2023-12-09T12:52:13+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "4a81eb9c42018f2dd746ac2181ec067f17b4cddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/4a81eb9c42018f2dd746ac2181ec067f17b4cddb", + "reference": "4a81eb9c42018f2dd746ac2181ec067f17b4cddb", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -4081,13 +4100,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/main" }, "funding": [ { @@ -4095,33 +4115,34 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2023-12-09T12:52:23+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "c341778c2bda41ce598dc02f0912876da5d07cd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c341778c2bda41ce598dc02f0912876da5d07cd3", + "reference": "c341778c2bda41ce598dc02f0912876da5d07cd3", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^4.10", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -4144,7 +4165,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/main" }, "funding": [ { @@ -4152,34 +4174,35 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-09T12:52:34+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "0c9ec982ff95cc83cce2a7811be6c07398bcf353" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/0c9ec982ff95cc83cce2a7811be6c07398bcf353", + "reference": "0c9ec982ff95cc83cce2a7811be6c07398bcf353", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -4201,7 +4224,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/main" }, "funding": [ { @@ -4209,32 +4233,33 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2023-12-09T12:52:44+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "fd1560a9909ac46ac6c9bf4ef34a8c2d61cd989d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/fd1560a9909ac46ac6c9bf4ef34a8c2d61cd989d", + "reference": "fd1560a9909ac46ac6c9bf4ef34a8c2d61cd989d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -4256,7 +4281,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/main" }, "funding": [ { @@ -4264,32 +4290,33 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2023-12-09T12:52:54+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "c667db4d144287807077438891c97eb4af356e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c667db4d144287807077438891c97eb4af356e2e", + "reference": "c667db4d144287807077438891c97eb4af356e2e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -4319,7 +4346,8 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/main" }, "funding": [ { @@ -4327,87 +4355,33 @@ "type": "github" } ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "dev-main", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "20bdda85c7c585ab265c0c37ec052a019bae29c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/20bdda85c7c585ab265c0c37ec052a019bae29c4", - "reference": "20bdda85c7c585ab265c0c37ec052a019bae29c4", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/main" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-03-25T08:11:39+00:00" + "time": "2023-12-09T12:53:43+00:00" }, { "name": "sebastian/type", - "version": "3.2.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "81e33408e8812ba0abe464fbc071149ca24fe7e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81e33408e8812ba0abe464fbc071149ca24fe7e7", + "reference": "81e33408e8812ba0abe464fbc071149ca24fe7e7", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^10.0" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -4430,7 +4404,8 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/main" }, "funding": [ { @@ -4438,29 +4413,30 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2023-12-09T12:53:55+00:00" }, { "name": "sebastian/version", - "version": "3.0.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "11642ad6e1c849da06f3f1f77e57e236e1cf6c4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/11642ad6e1c849da06f3f1f77e57e236e1cf6c4f", + "reference": "11642ad6e1c849da06f3f1f77e57e236e1cf6c4f", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -4483,7 +4459,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/main" }, "funding": [ { @@ -4491,7 +4468,7 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2023-12-09T12:54:05+00:00" }, { "name": "symfony/console", @@ -4499,12 +4476,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "380ae25f02e34809fba16fa387191f1da9852778" + "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/380ae25f02e34809fba16fa387191f1da9852778", - "reference": "380ae25f02e34809fba16fa387191f1da9852778", + "url": "https://api.github.com/repos/symfony/console/zipball/0254811a143e6bc6c8deea08b589a7e68a37f625", + "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625", "shasum": "" }, "require": { @@ -4585,7 +4562,7 @@ "type": "tidelift" } ], - "time": "2023-11-02T13:23:55+00:00" + "time": "2023-12-10T16:15:48+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4637,7 +4614,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -4794,7 +4771,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" }, "funding": [ { @@ -5510,12 +5487,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "a91571ff5df8825fcc74569d99cddc7242f479b7" + "reference": "c3649cf286a89a8c67cb9789011e9b36733f0e3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/a91571ff5df8825fcc74569d99cddc7242f479b7", - "reference": "a91571ff5df8825fcc74569d99cddc7242f479b7", + "url": "https://api.github.com/repos/symfony/process/zipball/c3649cf286a89a8c67cb9789011e9b36733f0e3e", + "reference": "c3649cf286a89a8c67cb9789011e9b36733f0e3e", "shasum": "" }, "require": { @@ -5563,7 +5540,7 @@ "type": "tidelift" } ], - "time": "2023-11-04T20:16:32+00:00" + "time": "2023-12-02T12:49:56+00:00" }, { "name": "symfony/service-contracts", @@ -5571,12 +5548,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a4025a1c812c231d88ed0780e866b0cc644f4a84" + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a4025a1c812c231d88ed0780e866b0cc644f4a84", - "reference": "a4025a1c812c231d88ed0780e866b0cc644f4a84", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838", + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838", "shasum": "" }, "require": { @@ -5630,7 +5607,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/main" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.0" }, "funding": [ { @@ -5646,7 +5623,7 @@ "type": "tidelift" } ], - "time": "2023-07-29T13:12:44+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/stopwatch", @@ -5716,12 +5693,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "afab7ffd61240b6fee63ea207b2b98c84f12791e" + "reference": "7cb80bc10bfcdf6b5492741c0b9357dac66940bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/afab7ffd61240b6fee63ea207b2b98c84f12791e", - "reference": "afab7ffd61240b6fee63ea207b2b98c84f12791e", + "url": "https://api.github.com/repos/symfony/string/zipball/7cb80bc10bfcdf6b5492741c0b9357dac66940bc", + "reference": "7cb80bc10bfcdf6b5492741c0b9357dac66940bc", "shasum": "" }, "require": { @@ -5794,20 +5771,20 @@ "type": "tidelift" } ], - "time": "2023-11-09T08:51:37+00:00" + "time": "2023-12-10T16:15:48+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -5836,7 +5813,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -5844,7 +5821,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], @@ -5852,9 +5829,7 @@ "stability-flags": { "shish/gqla": 20, "naroga/redis-cache": 20, - "tbela99/css": 20, - "scrutinizer/ocular": 20, - "phpstan/phpstan": 20 + "tbela99/css": 20 }, "prefer-stable": false, "prefer-lowest": false, @@ -5868,5 +5843,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/core/tests/basepage.test.php b/core/tests/BasePageTest.php similarity index 100% rename from core/tests/basepage.test.php rename to core/tests/BasePageTest.php diff --git a/core/tests/block.test.php b/core/tests/BlockTest.php similarity index 100% rename from core/tests/block.test.php rename to core/tests/BlockTest.php diff --git a/core/tests/init.test.php b/core/tests/InitTest.php similarity index 91% rename from core/tests/init.test.php rename to core/tests/InitTest.php index 02d3a0fffb..3fc48857be 100644 --- a/core/tests/init.test.php +++ b/core/tests/InitTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; -class TestInit extends TestCase +class InitTest extends TestCase { public function testInitExt() { diff --git a/core/tests/polyfills.test.php b/core/tests/PolyfillsTest.php similarity index 100% rename from core/tests/polyfills.test.php rename to core/tests/PolyfillsTest.php diff --git a/core/tests/tag.test.php b/core/tests/TagTest.php similarity index 100% rename from core/tests/tag.test.php rename to core/tests/TagTest.php diff --git a/core/tests/urls.test.php b/core/tests/UrlsTest.php similarity index 100% rename from core/tests/urls.test.php rename to core/tests/UrlsTest.php diff --git a/core/tests/util.test.php b/core/tests/UtilTest.php similarity index 100% rename from core/tests/util.test.php rename to core/tests/UtilTest.php diff --git a/ext/image_hash_ban/test.php b/ext/image_hash_ban/test.php index 5c8e433fa4..5ba09b80d9 100644 --- a/ext/image_hash_ban/test.php +++ b/ext/image_hash_ban/test.php @@ -49,7 +49,7 @@ public function testBan() $this->assertEquals(200, $page->code); } - public function onNotSuccessfulTest(\Throwable $t): void + public function onNotSuccessfulTest(\Throwable $t): never { send_event(new RemoveImageHashBanEvent($this->hash)); parent::onNotSuccessfulTest($t); // TODO: Change the autogenerated stub diff --git a/ext/index/test.php b/ext/index/test.php index 6f21fa6249..47146e7a95 100644 --- a/ext/index/test.php +++ b/ext/index/test.php @@ -4,6 +4,8 @@ namespace Shimmie2; +use PHPUnit\Framework\Attributes\Depends; + class IndexTest extends ShimmiePHPUnitTestCase { public function testIndexPage() @@ -79,21 +81,21 @@ public function testUpload(): array /* * * * * * * * * * * * Tag Search * * * * * * * * * * * */ - /** @depends testUpload */ + #[Depends('testUpload')] public function testTagSearchNoResults($image_ids) { $this->testUpload(); $this->assert_search_results(["maumaumau"], []); } - /** @depends testUpload */ + #[Depends('testUpload')] public function testTagSearchOneResult($image_ids) { $image_ids = $this->testUpload(); $this->assert_search_results(["pbx"], [$image_ids[0]]); } - /** @depends testUpload */ + #[Depends('testUpload')] public function testTagSearchManyResults($image_ids) { $image_ids = $this->testUpload(); @@ -103,7 +105,7 @@ public function testTagSearchManyResults($image_ids) /* * * * * * * * * * * * Multi-Tag Search * * * * * * * * * * * */ - /** @depends testUpload */ + #[Depends('testUpload')] public function testMultiTagSearchNoResults($image_ids) { $this->testUpload(); @@ -112,14 +114,14 @@ public function testMultiTagSearchNoResults($image_ids) $this->assert_search_results(["computer", "asdfasdfwaffle"], []); } - /** @depends testUpload */ + #[Depends('testUpload')] public function testMultiTagSearchOneResult($image_ids) { $image_ids = $this->testUpload(); $this->assert_search_results(["computer", "screenshot"], [$image_ids[0]]); } - /** @depends testUpload */ + #[Depends('testUpload')] public function testMultiTagSearchManyResults($image_ids) { $image_ids = $this->testUpload(); @@ -129,7 +131,7 @@ public function testMultiTagSearchManyResults($image_ids) /* * * * * * * * * * * * Meta Search * * * * * * * * * * * */ - /** @depends testUpload */ + #[Depends('testUpload')] public function testMetaSearchNoResults($image_ids) { $this->testUpload(); @@ -137,7 +139,7 @@ public function testMetaSearchNoResults($image_ids) $this->assert_search_results(["ratio=42:12345"], []); } - /** @depends testUpload */ + #[Depends('testUpload')] public function testMetaSearchOneResult($image_ids) { $image_ids = $this->testUpload(); @@ -147,7 +149,7 @@ public function testMetaSearchOneResult($image_ids) $this->assert_search_results(["filename=screenshot"], [$image_ids[0]]); } - /** @depends testUpload */ + #[Depends('testUpload')] public function testMetaSearchManyResults($image_ids) { $image_ids = $this->testUpload(); @@ -159,14 +161,14 @@ public function testMetaSearchManyResults($image_ids) /* * * * * * * * * * * * Wildcards * * * * * * * * * * * */ - /** @depends testUpload */ + #[Depends('testUpload')] public function testWildSearchNoResults($image_ids) { $this->testUpload(); $this->assert_search_results(["asdfasdf*"], []); } - /** @depends testUpload */ + #[Depends('testUpload')] public function testWildSearchOneResult($image_ids) { $image_ids = $this->testUpload(); @@ -175,7 +177,7 @@ public function testWildSearchOneResult($image_ids) $this->assert_search_results(["comp*", "screenshot"], [$image_ids[0]]); } - /** @depends testUpload */ + #[Depends('testUpload')] public function testWildSearchManyResults($image_ids) { $image_ids = $this->testUpload(); @@ -187,7 +189,7 @@ public function testWildSearchManyResults($image_ids) /* * * * * * * * * * * * Mixed * * * * * * * * * * * */ - /** @depends testUpload */ + #[Depends('testUpload')] public function testMixedSearchTagMeta($image_ids) { $image_ids = $this->testUpload(); @@ -200,7 +202,7 @@ public function testMixedSearchTagMeta($image_ids) /* * * * * * * * * * * * Negative * * * * * * * * * * * */ - /** @depends testUpload */ + #[Depends('testUpload')] public function testNegative($image_ids) { $image_ids = $this->testUpload(); diff --git a/ext/pools/test.php b/ext/pools/test.php index 67da7b489d..a10f4cab21 100644 --- a/ext/pools/test.php +++ b/ext/pools/test.php @@ -4,6 +4,8 @@ namespace Shimmie2; +use PHPUnit\Framework\Attributes\Depends; + class PoolsTest extends ShimmiePHPUnitTestCase { public function setUp(): void @@ -50,7 +52,7 @@ public function testCreate(): array return [$pool_id, [$image_id_1, $image_id_2]]; } - /** @depends testCreate */ + #[Depends('testCreate')] public function testOnViewImage($args) { [$pool_id, $image_ids] = $this->testCreate(); @@ -64,7 +66,7 @@ public function testOnViewImage($args) $this->assert_text("Pool"); } - /** @depends testCreate */ + #[Depends('testCreate')] public function testSearch($args) { [$pool_id, $image_ids] = $this->testCreate(); @@ -76,7 +78,7 @@ public function testSearch($args) $this->assert_text("Pool"); } - /** @depends testCreate */ + #[Depends('testCreate')] public function testList($args) { $this->testCreate(); @@ -84,7 +86,7 @@ public function testList($args) $this->assert_text("Pool"); } - /** @depends testCreate */ + #[Depends('testCreate')] public function testView($args) { [$pool_id, $image_ids] = $this->testCreate(); @@ -93,7 +95,7 @@ public function testView($args) $this->assert_text("Pool"); } - /** @depends testCreate */ + #[Depends('testCreate')] public function testHistory($args) { [$pool_id, $image_ids] = $this->testCreate(); @@ -102,7 +104,7 @@ public function testHistory($args) $this->assert_text("Pool"); } - /** @depends testCreate */ + #[Depends('testCreate')] public function testImport($args) { [$pool_id, $image_ids] = $this->testCreate(); @@ -114,7 +116,7 @@ public function testImport($args) $this->assert_text("Pool"); } - /** @depends testCreate */ + #[Depends('testCreate')] public function testRemovePosts($args): array { [$pool_id, $image_ids] = $this->testCreate(); @@ -128,7 +130,7 @@ public function testRemovePosts($args): array return [$pool_id, $image_ids]; } - /** @depends testRemovePosts */ + #[Depends('testRemovePosts')] public function testAddPosts($args) { [$pool_id, $image_ids] = $this->testRemovePosts(null); @@ -140,7 +142,7 @@ public function testAddPosts($args) $this->assertEquals(PageMode::REDIRECT, $page->mode); } - /** @depends testCreate */ + #[Depends('testCreate')] public function testEditDescription($args): array { [$pool_id, $image_ids] = $this->testCreate(); diff --git a/ext/relationships/test.php b/ext/relationships/test.php index 6e24e06210..61bd83366c 100644 --- a/ext/relationships/test.php +++ b/ext/relationships/test.php @@ -4,6 +4,8 @@ namespace Shimmie2; +use PHPUnit\Framework\Attributes\Depends; + class RelationshipsTest extends ShimmiePHPUnitTestCase { //================================================================= @@ -32,9 +34,7 @@ public function testNoParent(): array return [$image_1, $image_2, $image_3]; } - /** - * @depends testNoParent - */ + #[Depends('testNoParent')] public function testSetParent($imgs): array { [$image_1, $image_2, $image_3] = $this->testNoParent(); @@ -56,9 +56,7 @@ public function testSetParent($imgs): array return [$image_1, $image_2, $image_3]; } - /** - * @depends testSetParent - */ + #[Depends('testSetParent')] public function testChangeParent($imgs): array { [$image_1, $image_2, $image_3] = $this->testSetParent(null); @@ -79,9 +77,7 @@ public function testChangeParent($imgs): array return [$image_1, $image_2, $image_3]; } - /** - * @depends testSetParent - */ + #[Depends('testSetParent')] public function testSearch($imgs) { [$image_1, $image_2, $image_3] = $this->testSetParent(null); @@ -94,9 +90,7 @@ public function testSearch($imgs) $this->assert_search_results(["child:none"], [$image_3->id, $image_2->id]); } - /** - * @depends testChangeParent - */ + #[Depends('testChangeParent')] public function testRemoveParent($imgs) { [$image_1, $image_2, $image_3] = $this->testChangeParent(null); @@ -146,9 +140,7 @@ public function testSetParentByTagBase(): array return [$image_1, $image_2, $image_3]; } - /** - * @depends testSetParentByTagBase - */ + #[Depends('testSetParentByTagBase')] public function testSetParentByTag($imgs): array { [$image_1, $image_2, $image_3] = $this->testSetParentByTagBase(); @@ -171,9 +163,7 @@ public function testSetParentByTag($imgs): array return [$image_1, $image_2, $image_3]; } - /** - * @depends testSetParentByTag - */ + #[Depends('testSetParentByTag')] public function testSetChildByTag($imgs): array { [$image_1, $image_2, $image_3] = $this->testSetParentByTag(null); @@ -196,9 +186,7 @@ public function testSetChildByTag($imgs): array return [$image_1, $image_2, $image_3]; } - /** - * @depends testSetChildByTag - */ + #[Depends('testSetChildByTag')] public function testRemoveParentByTag($imgs) { [$image_1, $image_2, $image_3] = $this->testSetChildByTag(null); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2cd651ef36..7158383226 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -64,7 +64,7 @@ public static function setUpBeforeClass(): void public function setUp(): void { global $database, $_tracer; - $_tracer->begin($this->getName()); + $_tracer->begin($this->name()); $_tracer->begin("setUp"); $class = str_replace("Test", "", get_class($this)); if (!ExtensionInfo::get_for_extension_class($class)->is_supported()) { diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 2ded9aa855..d3420dc488 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -1,18 +1,19 @@ - - - - ../core - ../ext - ../themes/default - - + + - ../core/ + ../core/ ../ext/ + + + ../core + ../ext + ../themes/default + + From 4c8274161ffad622c9ddc813d1fc774f9cab32aa Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 02:08:50 +0000 Subject: [PATCH 003/154] fix more search edge-cases --- core/imageboard/image.php | 84 +++++++++++++++++++++++++-------------- ext/index/test.php | 26 +++++++++--- 2 files changed, 75 insertions(+), 35 deletions(-) diff --git a/core/imageboard/image.php b/core/imageboard/image.php index ec30eb64bc..fff41e9857 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -159,7 +159,8 @@ private static function find_images_internal(int $start = 0, ?int $limit = null, } } - $querylet = Image::build_search_querylet($tags, $limit, $start); + [$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags); + $querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order, $limit, $start); return $database->get_all_iterable($querylet->sql, $querylet->variables); } @@ -246,7 +247,8 @@ public static function count_images(array $tags = []): int if (Extension::is_enabled(RatingsInfo::KEY)) { $tags[] = "rating:*"; } - $querylet = Image::build_search_querylet($tags); + [$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags); + $querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order); $total = (int)$database->get_one("SELECT COUNT(*) AS cnt FROM ($querylet->sql) AS tbl", $querylet->variables); if (SPEED_HAX && $total > 5000) { // when we have a ton of images, the count @@ -307,7 +309,8 @@ public function get_next(array $tags = [], bool $next = true): ?Image } else { $tags[] = 'id'. $gtlt . $this->id; $tags[] = 'order:id_'. strtolower($dir); - $querylet = Image::build_search_querylet($tags); + [$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags); + $querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order); $querylet->append_sql(' LIMIT 1'); $row = $database->get_row($querylet->sql, $querylet->variables); } @@ -749,13 +752,13 @@ private static function tag_or_wildcard_to_ids(string $tag): array } /** + * Turn a human input string into a an abstract search query + * * @param string[] $terms + * @return array{0: TagCondition[], 1: ImgCondition[], 2: string} */ - private static function build_search_querylet( - array $terms, - ?int $limit = null, - ?int $offset = null - ): Querylet { + private static function terms_to_conditions(array $terms): array + { global $config; $tag_conditions = []; @@ -776,18 +779,30 @@ private static function build_search_querylet( $order = ($order ?: "images.".$config->get_string(IndexConfig::ORDER)); - /* - * Turn a bunch of Querylet objects into a base query - * - * Must follow the format - * - * SELECT images.* - * FROM (...) AS images - * WHERE (...) - * - * ie, return a set of images.* columns, and end with a WHERE - */ + return [$tag_conditions, $img_conditions, $order]; + } + /** + * Turn an abstract search query into an SQL Querylet + * + * Must follow the format + * + * SELECT images.* + * FROM (...) AS images + * WHERE (...) + * + * ie, return a set of images.* columns, and end with a WHERE + * + * @param TagCondition[] $tag_conditions + * @param ImgCondition[] $img_conditions + */ + private static function build_search_querylet( + array $tag_conditions, + array $img_conditions, + string $order, + ?int $limit = null, + ?int $offset = null + ): Querylet { // no tags, do a simple search if (count($tag_conditions) === 0) { $query = new Querylet("SELECT images.* FROM images WHERE 1=1"); @@ -796,15 +811,28 @@ private static function build_search_querylet( // one tag sorted by ID - we can fetch this from the image_tags table, // and do the offset / limit there, which is 10x faster than fetching // all the image_tags and doing the offset / limit on the result. + // + // NOTE: this is currently impossible to test, because the test suite + // loads all extensions, some of whom add generic img_conditions onto + // the search, which prevents this optimisation from being used. elseif ( count($tag_conditions) === 1 + && $tag_conditions[0]->positive + // We can only do this if img_conditions is empty, because + // we're going to apply the offset / limit to the image_tags + // subquery, and applying extra conditions to the top-level + // query might reduce the total results below the target limit && empty($img_conditions) + // We can only do this if we're sorting by ID, because + // we're going to be using the image_tags table, which + // only has image_id and tag_id, not any other columns && ($order == "id DESC" || $order == "images.id DESC") - && !is_null($offset) + // This is only an optimisation if we are applying limit + // and offset && !is_null($limit) + && !is_null($offset) ) { $tc = $tag_conditions[0]; - $in = $tc->positive ? "IN" : "NOT IN"; // IN (SELECT id FROM tags) is 100x slower than doing a separate // query and then a second query for IN(first_query_results)?? $tag_array = self::tag_or_wildcard_to_ids($tc->tag); @@ -820,17 +848,16 @@ private static function build_search_querylet( $query = new Querylet(" SELECT images.* FROM images INNER JOIN ( - SELECT it.image_id + SELECT DISTINCT it.image_id FROM image_tags it - WHERE it.tag_id $in ($set) + WHERE it.tag_id IN ($set) ORDER BY it.image_id DESC LIMIT :limit OFFSET :offset ) a on a.image_id = images.id - ORDER BY images.id DESC + WHERE 1=1 ", ["limit" => $limit, "offset" => $offset]); // don't offset at the image level because // we already offset at the image_tags level - $order = null; $limit = null; $offset = null; } @@ -890,7 +917,7 @@ private static function build_search_querylet( } $first = array_shift($inner_joins); - $sub_query = "SELECT it.image_id FROM image_tags it "; + $sub_query = "SELECT DISTINCT it.image_id FROM image_tags it "; $i = 0; foreach ($inner_joins as $inner_join) { $i++; @@ -946,9 +973,8 @@ private static function build_search_querylet( $query->append(new Querylet($img_sql, $img_vars)); } - if (!is_null($order)) { - $query->append(new Querylet(" ORDER BY ".$order)); - } + $query->append(new Querylet(" ORDER BY ".$order)); + if (!is_null($limit)) { $query->append(new Querylet(" LIMIT :limit ", ["limit" => $limit])); $query->append(new Querylet(" OFFSET :offset ", ["offset" => $offset])); diff --git a/ext/index/test.php b/ext/index/test.php index 47146e7a95..127b306aca 100644 --- a/ext/index/test.php +++ b/ext/index/test.php @@ -178,14 +178,21 @@ public function testWildSearchOneResult($image_ids) } #[Depends('testUpload')] - public function testWildSearchManyResults($image_ids) + public function testWildSearchManyResultsSimple($image_ids) { $image_ids = $this->testUpload(); - // two images match comp* - one matches it once, - // one matches it twice + // two images match comp* - one matches it once, one matches it twice $this->assert_search_results(["comp*"], [$image_ids[1], $image_ids[0]]); } + #[Depends('testUpload')] + public function testWildSearchManyResultsComplex($image_ids) + { + $image_ids = $this->testUpload(); + // same thing, but with the complex branch + $this->assert_search_results(["comp*", "-asdf"], [$image_ids[1], $image_ids[0]]); + } + /* * * * * * * * * * * * Mixed * * * * * * * * * * * */ @@ -196,20 +203,27 @@ public function testMixedSearchTagMeta($image_ids) // multiple tags, many results $this->assert_search_results(["computer", "size=640x480"], [$image_ids[1], $image_ids[0]]); } - // tag + negative - // wildcards + ??? /* * * * * * * * * * * * Negative * * * * * * * * * * * */ #[Depends('testUpload')] - public function testNegative($image_ids) + public function testSubtractFromSearch($image_ids) { $image_ids = $this->testUpload(); // negative tag, should have one result $this->assert_search_results(["computer", "-pbx"], [$image_ids[1]]); + // removing something that doesn't exist should have no effect + $this->assert_search_results(["computer", "-not_a_tag"], [$image_ids[1], $image_ids[0]]); + } + + #[Depends('testUpload')] + public function testSubtractFromDefault($image_ids) + { + $image_ids = $this->testUpload(); + // negative tag alone, should work $this->assert_search_results(["-pbx"], [$image_ids[1]]); From df0a8c28d9aecb3a520bbc279657b426a8117898 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 02:07:36 +0000 Subject: [PATCH 004/154] Add some useful extensions to devcontainer --- .devcontainer/devcontainer.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4a0f654d70..4f6b9d4495 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -21,12 +21,15 @@ "vscode": { "extensions": [ "recca0120.vscode-phpunit", - "ryanluker.vscode-coverage-gutters" + "ryanluker.vscode-coverage-gutters", + "xdebug.php-debug", + "DEVSENSE.phptools-vscode", + "ms-azuretools.vscode-docker" ], "settings": { "phpunit.args": [ - "--configuration", "${workspaceFolder}/tests/phpunit.xml"//, - //"--coverage-clover", "data/coverage.clover" + "--configuration", "${workspaceFolder}/tests/phpunit.xml", + "--coverage-clover", "data/coverage.clover" ], "coverage-gutters.coverageFileNames": [ "data/coverage.clover" From a5c6f1321b2fb02081d5e2355ee00f4c27bbedb3 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 16:33:21 +0000 Subject: [PATCH 005/154] refactor search a little and add much better testing --- core/extension.php | 6 +- core/imageboard/image.php | 387 +---------------------------- core/imageboard/search.php | 369 +++++++++++++++++++++++++++- core/tests/SearchTest.php | 490 +++++++++++++++++++++++++++++++++++++ ext/artists/main.php | 2 +- ext/bulk_actions/main.php | 2 +- ext/danbooru_api/main.php | 4 +- ext/favorites/main.php | 2 +- ext/home/main.php | 2 +- ext/image/main.php | 2 +- ext/index/main.php | 10 +- ext/index/test.php | 185 -------------- ext/numeric_score/main.php | 4 +- ext/ouroboros_api/main.php | 2 +- ext/pools/main.php | 2 +- ext/rating/main.php | 2 +- ext/regen_thumb/main.php | 2 +- ext/rss_images/main.php | 2 +- ext/rule34/main.php | 2 +- ext/shimmie_api/main.php | 4 +- ext/sitemap/main.php | 6 +- ext/tag_edit/main.php | 6 +- tests/bootstrap.php | 10 +- 23 files changed, 902 insertions(+), 601 deletions(-) create mode 100644 core/tests/SearchTest.php diff --git a/core/extension.php b/core/extension.php index cf246e8cb4..296934139d 100644 --- a/core/extension.php +++ b/core/extension.php @@ -110,6 +110,10 @@ protected function set_version(string $name, int $ver) } } +class ExtensionNotFound extends SCoreException +{ +} + enum ExtensionVisibility { case DEFAULT; @@ -235,7 +239,7 @@ public static function get_for_extension_class(string $base): ExtensionInfo return self::$all_info_by_class[$normal]; } else { $infos = print_r(array_keys(self::$all_info_by_class), true); - throw new SCoreException("$normal not found in {$infos}"); + throw new ExtensionNotFound("$normal not found in {$infos}"); } } diff --git a/core/imageboard/image.php b/core/imageboard/image.php index fff41e9857..12bbaae607 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -126,7 +126,7 @@ public static function by_id_or_hash(string $id): ?Image public static function by_random(array $tags = [], int $limit_range = 0): ?Image { - $max = Image::count_images($tags); + $max = Search::count_images($tags); if ($max < 1) { return null; } // From Issue #22 - opened by HungryFeline on May 30, 2011. @@ -134,7 +134,7 @@ public static function by_random(array $tags = [], int $limit_range = 0): ?Image $max = $limit_range; } $rand = mt_rand(0, $max - 1); - $set = Image::find_images($rand, 1, $tags); + $set = Search::find_images($rand, 1, $tags); if (count($set) > 0) { return $set[0]; } else { @@ -142,138 +142,6 @@ public static function by_random(array $tags = [], int $limit_range = 0): ?Image } } - private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags = []): iterable - { - global $database, $user; - - 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 PermissionDeniedException("Anonymous users may only search for up to 3 tags at a time"); - } - } - - [$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags); - $querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order, $limit, $start); - return $database->get_all_iterable($querylet->sql, $querylet->variables); - } - - /** - * Search for an array of images - * - * @param string[] $tags - * @return Image[] - */ - #[Query(name: "posts", type: "[Post!]!", args: ["tags" => "[string!]"])] - public static function find_images(int $offset = 0, ?int $limit = null, array $tags = []): array - { - $result = self::find_images_internal($offset, $limit, $tags); - - $images = []; - foreach ($result as $row) { - $images[] = new Image($row); - } - return $images; - } - - /** - * Search for an array of images, returning a iterable object of Image - */ - public static function find_images_iterable(int $start = 0, ?int $limit = null, array $tags = []): \Generator - { - $result = self::find_images_internal($start, $limit, $tags); - foreach ($result as $row) { - yield new Image($row); - } - } - - /* - * Image-related utility functions - */ - - public static function count_total_images(): int - { - global $cache, $database; - $total = $cache->get("image-count"); - if (is_null($total)) { - $total = (int)$database->get_one("SELECT COUNT(*) FROM images"); - $cache->set("image-count", $total, 600); - } - return $total; - } - - public static function count_tag(string $tag): int - { - global $database; - return (int)$database->get_one( - "SELECT count FROM tags WHERE LOWER(tag) = LOWER(:tag)", - ["tag" => $tag] - ); - } - - /** - * Count the number of image results for a given search - * - * @param string[] $tags - */ - public static function count_images(array $tags = []): int - { - global $cache, $database; - $tag_count = count($tags); - - if (SPEED_HAX && $tag_count === 0) { - // total number of images in the DB - $total = self::count_total_images(); - } elseif (SPEED_HAX && $tag_count === 1 && !preg_match("/[:=><\*\?]/", $tags[0])) { - if (!str_starts_with($tags[0], "-")) { - // one tag - we can look that up directly - $total = self::count_tag($tags[0]); - } else { - // one negative tag - subtract from the total - $total = self::count_total_images() - self::count_tag(substr($tags[0], 1)); - } - } else { - // complex query - // implode(tags) can be too long for memcache... - $cache_key = "image-count:" . md5(Tag::implode($tags)); - $total = $cache->get($cache_key); - if (is_null($total)) { - if (Extension::is_enabled(RatingsInfo::KEY)) { - $tags[] = "rating:*"; - } - [$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags); - $querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order); - $total = (int)$database->get_one("SELECT COUNT(*) AS cnt FROM ($querylet->sql) AS tbl", $querylet->variables); - if (SPEED_HAX && $total > 5000) { - // when we have a ton of images, the count - // won't change dramatically very often - $cache->set($cache_key, $total, 3600); - } - } - } - if (is_null($total)) { - return 0; - } - return $total; - } - - /** - * Count the number of pages for a given search - * - * @param string[] $tags - */ - public static function count_pages(array $tags = []): int - { - global $config; - return (int)ceil(Image::count_images($tags) / $config->get_int(IndexConfig::IMAGES)); - } - /* * Accessors & mutators */ @@ -306,16 +174,13 @@ public function get_next(array $tags = [], bool $next = true): ?Image ORDER BY images.id '.$dir.' LIMIT 1 '); + return ($row ? new Image($row) : null); } else { $tags[] = 'id'. $gtlt . $this->id; $tags[] = 'order:id_'. strtolower($dir); - [$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags); - $querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order); - $querylet->append_sql(' LIMIT 1'); - $row = $database->get_row($querylet->sql, $querylet->variables); + $images = Search::find_images(0, 1, $tags); + return (count($images) > 0) ? $images[0] : null; } - - return ($row ? new Image($row) : null); } /** @@ -740,246 +605,4 @@ public function parse_link_template(string $tmpl, int $n = 0): string $tmpl = $plte->link; return load_balance_url($tmpl, $this->hash, $n); } - - private static function tag_or_wildcard_to_ids(string $tag): array - { - global $database; - $sq = "SELECT id FROM tags WHERE LOWER(tag) LIKE LOWER(:tag)"; - if ($database->get_driver_id() === DatabaseDriverID::SQLITE) { - $sq .= "ESCAPE '\\'"; - } - return $database->get_col($sq, ["tag" => Tag::sqlify($tag)]); - } - - /** - * Turn a human input string into a an abstract search query - * - * @param string[] $terms - * @return array{0: TagCondition[], 1: ImgCondition[], 2: string} - */ - private static function terms_to_conditions(array $terms): array - { - global $config; - - $tag_conditions = []; - $img_conditions = []; - $order = null; - - /* - * Turn a bunch of strings into a bunch of TagCondition - * and ImgCondition objects - */ - $stpen = 0; // search term parse event number - foreach (array_merge([null], $terms) as $term) { - $stpe = send_event(new SearchTermParseEvent($stpen++, $term, $terms)); - $order ??= $stpe->order; - $img_conditions = array_merge($img_conditions, $stpe->img_conditions); - $tag_conditions = array_merge($tag_conditions, $stpe->tag_conditions); - } - - $order = ($order ?: "images.".$config->get_string(IndexConfig::ORDER)); - - return [$tag_conditions, $img_conditions, $order]; - } - - /** - * Turn an abstract search query into an SQL Querylet - * - * Must follow the format - * - * SELECT images.* - * FROM (...) AS images - * WHERE (...) - * - * ie, return a set of images.* columns, and end with a WHERE - * - * @param TagCondition[] $tag_conditions - * @param ImgCondition[] $img_conditions - */ - private static function build_search_querylet( - array $tag_conditions, - array $img_conditions, - string $order, - ?int $limit = null, - ?int $offset = null - ): Querylet { - // no tags, do a simple search - if (count($tag_conditions) === 0) { - $query = new Querylet("SELECT images.* FROM images WHERE 1=1"); - } - - // one tag sorted by ID - we can fetch this from the image_tags table, - // and do the offset / limit there, which is 10x faster than fetching - // all the image_tags and doing the offset / limit on the result. - // - // NOTE: this is currently impossible to test, because the test suite - // loads all extensions, some of whom add generic img_conditions onto - // the search, which prevents this optimisation from being used. - elseif ( - count($tag_conditions) === 1 - && $tag_conditions[0]->positive - // We can only do this if img_conditions is empty, because - // we're going to apply the offset / limit to the image_tags - // subquery, and applying extra conditions to the top-level - // query might reduce the total results below the target limit - && empty($img_conditions) - // We can only do this if we're sorting by ID, because - // we're going to be using the image_tags table, which - // only has image_id and tag_id, not any other columns - && ($order == "id DESC" || $order == "images.id DESC") - // This is only an optimisation if we are applying limit - // and offset - && !is_null($limit) - && !is_null($offset) - ) { - $tc = $tag_conditions[0]; - // IN (SELECT id FROM tags) is 100x slower than doing a separate - // query and then a second query for IN(first_query_results)?? - $tag_array = self::tag_or_wildcard_to_ids($tc->tag); - if (count($tag_array) == 0) { - // if wildcard expanded to nothing, take a shortcut - if ($tc->positive) { - $query = new Querylet("SELECT images.* FROM images WHERE 1=0"); - } else { - $query = new Querylet("SELECT images.* FROM images WHERE 1=1"); - } - } else { - $set = implode(', ', $tag_array); - $query = new Querylet(" - SELECT images.* - FROM images INNER JOIN ( - SELECT DISTINCT it.image_id - FROM image_tags it - WHERE it.tag_id IN ($set) - ORDER BY it.image_id DESC - LIMIT :limit OFFSET :offset - ) a on a.image_id = images.id - WHERE 1=1 - ", ["limit" => $limit, "offset" => $offset]); - // don't offset at the image level because - // we already offset at the image_tags level - $limit = null; - $offset = null; - } - } - - // more than one tag, or more than zero other conditions, or a non-default sort order - else { - $positive_tag_id_array = []; - $positive_wildcard_id_array = []; - $negative_tag_id_array = []; - $all_nonexistent_negatives = true; - - foreach ($tag_conditions as $tq) { - $tag_ids = self::tag_or_wildcard_to_ids($tq->tag); - $tag_count = count($tag_ids); - - if ($tq->positive) { - $all_nonexistent_negatives = false; - 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("SELECT images.* 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 { - if ($tag_count > 0) { - $all_nonexistent_negatives = false; - // 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 || $positive_wildcard_id_array || $negative_tag_id_array || $all_nonexistent_negatives, @$_GET['q']); - - if ($all_nonexistent_negatives) { - $query = new Querylet("SELECT images.* FROM images WHERE 1=1"); - } elseif (!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 DISTINCT 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 "; - - $query = new Querylet(" - 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); - $query = new Querylet(" - 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"); - } - } - - /* - * Merge all the image metadata searches into one generic querylet - * and append to the base querylet with "AND blah" - */ - if (!empty($img_conditions)) { - $n = 0; - $img_sql = ""; - $img_vars = []; - foreach ($img_conditions as $iq) { - if ($n++ > 0) { - $img_sql .= " AND"; - } - if (!$iq->positive) { - $img_sql .= " NOT"; - } - $img_sql .= " (" . $iq->qlet->sql . ")"; - $img_vars = array_merge($img_vars, $iq->qlet->variables); - } - $query->append_sql(" AND "); - $query->append(new Querylet($img_sql, $img_vars)); - } - - $query->append(new Querylet(" ORDER BY ".$order)); - - if (!is_null($limit)) { - $query->append(new Querylet(" LIMIT :limit ", ["limit" => $limit])); - $query->append(new Querylet(" OFFSET :offset ", ["offset" => $offset])); - } - - return $query; - } } diff --git a/core/imageboard/search.php b/core/imageboard/search.php index 624f52237b..d6af5815eb 100644 --- a/core/imageboard/search.php +++ b/core/imageboard/search.php @@ -4,6 +4,8 @@ namespace Shimmie2; +use GQLA\Query; + class Querylet { public function __construct( @@ -33,7 +35,7 @@ class TagCondition { public function __construct( public string $tag, - public bool $positive, + public bool $positive = true, ) { } } @@ -42,7 +44,370 @@ class ImgCondition { public function __construct( public Querylet $qlet, - public bool $positive, + public bool $positive = true, ) { } } + +class Search +{ + public static array $_search_path = []; + + private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags = []): iterable + { + global $database, $user; + + 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 PermissionDeniedException("Anonymous users may only search for up to 3 tags at a time"); + } + } + + [$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags); + $querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order, $limit, $start); + return $database->get_all_iterable($querylet->sql, $querylet->variables); + } + + /** + * Search for an array of images + * + * @param string[] $tags + * @return Image[] + */ + #[Query(name: "posts", type: "[Post!]!", args: ["tags" => "[string!]"])] + public static function find_images(int $offset = 0, ?int $limit = null, array $tags = []): array + { + $result = self::find_images_internal($offset, $limit, $tags); + + $images = []; + foreach ($result as $row) { + $images[] = new Image($row); + } + return $images; + } + + /** + * Search for an array of images, returning a iterable object of Image + */ + public static function find_images_iterable(int $start = 0, ?int $limit = null, array $tags = []): \Generator + { + $result = self::find_images_internal($start, $limit, $tags); + foreach ($result as $row) { + yield new Image($row); + } + } + + /* + * Image-related utility functions + */ + + public static function count_tag(string $tag): int + { + global $database; + return (int)$database->get_one( + "SELECT count FROM tags WHERE LOWER(tag) = LOWER(:tag)", + ["tag" => $tag] + ); + } + + private static function count_total_images(): int + { + global $cache, $database; + $total = $cache->get("image-count"); + if (is_null($total)) { + $total = (int)$database->get_one("SELECT COUNT(*) FROM images"); + $cache->set("image-count", $total, 600); + } + return $total; + } + + /** + * Count the number of image results for a given search + * + * @param string[] $tags + */ + public static function count_images(array $tags = []): int + { + global $cache, $database; + $tag_count = count($tags); + + // SPEED_HAX ignores the fact that extensions can add img_conditions + // even when there are no tags being searched for + if (SPEED_HAX && $tag_count === 0) { + // total number of images in the DB + $total = self::count_total_images(); + } elseif (SPEED_HAX && $tag_count === 1 && !preg_match("/[:=><\*\?]/", $tags[0])) { + if (!str_starts_with($tags[0], "-")) { + // one positive tag - we can look that up directly + $total = self::count_tag($tags[0]); + } else { + // one negative tag - subtract from the total + $total = self::count_total_images() - self::count_tag(substr($tags[0], 1)); + } + } else { + // complex query + // implode(tags) can be too long for memcache, so use the hash of tags as the key + $cache_key = "image-count:" . md5(Tag::implode($tags)); + $total = $cache->get($cache_key); + if (is_null($total)) { + if (Extension::is_enabled(RatingsInfo::KEY)) { + $tags[] = "rating:*"; + } + [$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags); + $querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order); + $total = (int)$database->get_one("SELECT COUNT(*) AS cnt FROM ($querylet->sql) AS tbl", $querylet->variables); + if (SPEED_HAX && $total > 5000) { + // when we have a ton of images, the count + // won't change dramatically very often + $cache->set($cache_key, $total, 3600); + } + } + } + if (is_null($total)) { + return 0; + } + return $total; + } + + + private static function tag_or_wildcard_to_ids(string $tag): array + { + global $database; + $sq = "SELECT id FROM tags WHERE LOWER(tag) LIKE LOWER(:tag)"; + if ($database->get_driver_id() === DatabaseDriverID::SQLITE) { + $sq .= "ESCAPE '\\'"; + } + return $database->get_col($sq, ["tag" => Tag::sqlify($tag)]); + } + + /** + * Turn a human input string into a an abstract search query + * + * @param string[] $terms + * @return array{0: TagCondition[], 1: ImgCondition[], 2: string} + */ + private static function terms_to_conditions(array $terms): array + { + global $config; + + $tag_conditions = []; + $img_conditions = []; + $order = null; + + /* + * Turn a bunch of strings into a bunch of TagCondition + * and ImgCondition objects + */ + $stpen = 0; // search term parse event number + foreach (array_merge([null], $terms) as $term) { + $stpe = send_event(new SearchTermParseEvent($stpen++, $term, $terms)); + $order ??= $stpe->order; + $img_conditions = array_merge($img_conditions, $stpe->img_conditions); + $tag_conditions = array_merge($tag_conditions, $stpe->tag_conditions); + } + + $order = ($order ?: "images.".$config->get_string(IndexConfig::ORDER)); + + return [$tag_conditions, $img_conditions, $order]; + } + + /** + * Turn an abstract search query into an SQL Querylet + * + * @param TagCondition[] $tag_conditions + * @param ImgCondition[] $img_conditions + */ + private static function build_search_querylet( + array $tag_conditions, + array $img_conditions, + string $order, + ?int $limit = null, + ?int $offset = null + ): Querylet { + // no tags, do a simple search + if (count($tag_conditions) === 0) { + static::$_search_path[] = "no_tags"; + $query = new Querylet("SELECT images.* FROM images WHERE 1=1"); + } + + // one tag sorted by ID - we can fetch this from the image_tags table, + // and do the offset / limit there, which is 10x faster than fetching + // all the image_tags and doing the offset / limit on the result. + elseif ( + count($tag_conditions) === 1 + && $tag_conditions[0]->positive + // We can only do this if img_conditions is empty, because + // we're going to apply the offset / limit to the image_tags + // subquery, and applying extra conditions to the top-level + // query might reduce the total results below the target limit + && empty($img_conditions) + // We can only do this if we're sorting by ID, because + // we're going to be using the image_tags table, which + // only has image_id and tag_id, not any other columns + && ($order == "id DESC" || $order == "images.id DESC") + // This is only an optimisation if we are applying limit + // and offset + && !is_null($limit) + && !is_null($offset) + ) { + static::$_search_path[] = "fast"; + $tc = $tag_conditions[0]; + // IN (SELECT id FROM tags) is 100x slower than doing a separate + // query and then a second query for IN(first_query_results)?? + $tag_array = self::tag_or_wildcard_to_ids($tc->tag); + if (count($tag_array) == 0) { + // if wildcard expanded to nothing, take a shortcut + static::$_search_path[] = "invalid_tag"; + $query = new Querylet("SELECT images.* FROM images WHERE 1=0"); + } else { + $set = implode(', ', $tag_array); + $query = new Querylet(" + SELECT images.* + FROM images INNER JOIN ( + SELECT DISTINCT it.image_id + FROM image_tags it + WHERE it.tag_id IN ($set) + ORDER BY it.image_id DESC + LIMIT :limit OFFSET :offset + ) a on a.image_id = images.id + WHERE 1=1 + ", ["limit" => $limit, "offset" => $offset]); + // don't offset at the image level because + // we already offset at the image_tags level + $limit = null; + $offset = null; + } + } + + // more than one tag, or more than zero other conditions, or a non-default sort order + else { + static::$_search_path[] = "general"; + $positive_tag_id_array = []; + $positive_wildcard_id_array = []; + $negative_tag_id_array = []; + $all_nonexistent_negatives = true; + + foreach ($tag_conditions as $tq) { + $tag_ids = self::tag_or_wildcard_to_ids($tq->tag); + $tag_count = count($tag_ids); + + if ($tq->positive) { + $all_nonexistent_negatives = false; + if ($tag_count == 0) { + # one of the positive tags had zero results, therefor there + # can be no results; "where 1=0" should shortcut things + static::$_search_path[] = "invalid_tag"; + return new Querylet("SELECT images.* 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 { + if ($tag_count > 0) { + $all_nonexistent_negatives = false; + // 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 || $positive_wildcard_id_array || $negative_tag_id_array || $all_nonexistent_negatives, @$_GET['q']); + + if ($all_nonexistent_negatives) { + static::$_search_path[] = "all_nonexistent_negatives"; + $query = new Querylet("SELECT images.* FROM images WHERE 1=1"); + } elseif (!empty($positive_tag_id_array) || !empty($positive_wildcard_id_array)) { + static::$_search_path[] = "some_positives"; + $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 DISTINCT 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 "; + + $query = new Querylet(" + SELECT images.* + FROM images + INNER JOIN ($sub_query) a on a.image_id = images.id + "); + } elseif (!empty($negative_tag_id_array)) { + static::$_search_path[] = "only_negative_tags"; + $negative_tag_id_list = join(', ', $negative_tag_id_array); + $query = new Querylet(" + 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"); + } + } + + /* + * Merge all the image metadata searches into one generic querylet + * and append to the base querylet with "AND blah" + */ + if (!empty($img_conditions)) { + $n = 0; + $img_sql = ""; + $img_vars = []; + foreach ($img_conditions as $iq) { + if ($n++ > 0) { + $img_sql .= " AND"; + } + if (!$iq->positive) { + $img_sql .= " NOT"; + } + $img_sql .= " (" . $iq->qlet->sql . ")"; + $img_vars = array_merge($img_vars, $iq->qlet->variables); + } + $query->append_sql(" AND "); + $query->append(new Querylet($img_sql, $img_vars)); + } + + $query->append(new Querylet(" ORDER BY ".$order)); + + if (!is_null($limit)) { + $query->append(new Querylet(" LIMIT :limit ", ["limit" => $limit])); + $query->append(new Querylet(" OFFSET :offset ", ["offset" => $offset])); + } + + return $query; + } +} diff --git a/core/tests/SearchTest.php b/core/tests/SearchTest.php new file mode 100644 index 0000000000..303072f7b4 --- /dev/null +++ b/core/tests/SearchTest.php @@ -0,0 +1,490 @@ +log_in_as_user(); + $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "question? colon:thing exclamation!"); + $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "question. colon_thing exclamation%"); + + $this->assert_search_results(["question?"], [$image_id_1]); + $this->assert_search_results(["question."], [$image_id_2]); + $this->assert_search_results(["colon:thing"], [$image_id_1]); + $this->assert_search_results(["colon_thing"], [$image_id_2]); + $this->assert_search_results(["exclamation!"], [$image_id_1]); + $this->assert_search_results(["exclamation%"], [$image_id_2]); + } + + // base case + public function testUpload(): array + { + $this->log_in_as_user(); + $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "thing computer screenshot pbx phone"); + $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "thing computer computing bedroom workshop"); + $this->log_out(); + + # make sure both uploads were ok + $this->assertTrue($image_id_1 > 0); + $this->assertTrue($image_id_2 > 0); + + return [$image_id_1, $image_id_2]; + } + + + /******************************************************** + * Test turning a string into an abstract query + */ + private function assert_TTC(string $tags, array $expected_tag_conditions, array $expected_img_conditions, string $expected_order) + { + $class = new \ReflectionClass('\Shimmie2\Search'); + $terms_to_conditions = $class->getMethod("terms_to_conditions"); + $terms_to_conditions->setAccessible(true); // Use this if you are running PHP older than 8.1.0 + + $obj = new Search(); + [$tag_conditions, $img_conditions, $order] = $terms_to_conditions->invokeArgs($obj, [Tag::explode($tags, false)]); + + static::assertThat( + [ + "tags" => $expected_tag_conditions, + "imgs" => $expected_img_conditions, + "order" => $expected_order, + ], + new IsEqual([ + "tags" => $tag_conditions, + "imgs" => $img_conditions, + "order" => $order, + ]) + ); + } + + public function testTTC_Empty() + { + $this->assert_TTC( + "", + [ + ], + [ + new ImgCondition(new Querylet("trash != :true", ["true" => true])), + new ImgCondition(new Querylet("private != :true OR owner_id = :private_owner_id", [ + "private_owner_id" => 1, + "true" => true])), + new ImgCondition(new Querylet("rating IN ('?', 's', 'q', 'e')", [])), + ], + "images.id DESC" + ); + } + + public function testTTC_Hash() + { + $this->assert_TTC( + "hash=1234567890", + [ + ], + [ + new ImgCondition(new Querylet("trash != :true", ["true" => true])), + new ImgCondition(new Querylet("private != :true OR owner_id = :private_owner_id", [ + "private_owner_id" => 1, + "true" => true])), + new ImgCondition(new Querylet("rating IN ('?', 's', 'q', 'e')", [])), + new ImgCondition(new Querylet("images.hash = :hash", ["hash" => "1234567890"])), + ], + "images.id DESC" + ); + } + + public function testTTC_Ratio() + { + $this->assert_TTC( + "ratio=42:12345", + [ + ], + [ + new ImgCondition(new Querylet("trash != :true", ["true" => true])), + new ImgCondition(new Querylet("private != :true OR owner_id = :private_owner_id", [ + "private_owner_id" => 1, + "true" => true])), + new ImgCondition(new Querylet("rating IN ('?', 's', 'q', 'e')", [])), + new ImgCondition(new Querylet("width / :width1 = height / :height1", ['width1' => 42, + 'height1' => 12345])), + ], + "images.id DESC" + ); + } + + public function testTTC_Order() + { + $this->assert_TTC( + "order=score", + [ + ], + [ + new ImgCondition(new Querylet("trash != :true", ["true" => true])), + new ImgCondition(new Querylet("private != :true OR owner_id = :private_owner_id", [ + "private_owner_id" => 1, + "true" => true])), + new ImgCondition(new Querylet("rating IN ('?', 's', 'q', 'e')", [])), + ], + "images.numeric_score DESC" + ); + } + + /******************************************************** + * Test turning an abstract query into SQL + fetching the results + */ + private function assert_BSQ( + array $tcs = [], + array $ics = [], + string $order = "id DESC", + int $limit = 9999, + int $start = 0, + array $res = [], + array $path = null, + ) { + global $database; + + $tcs = array_map( + fn ($tag) => ($tag[0] == "-") ? + new TagCondition(substr($tag, 1), false) : + new TagCondition($tag), + $tcs + ); + + $ics = array_map( + fn ($ic) => send_event(new SearchTermParseEvent(0, $ic, []))->img_conditions, + $ics + ); + $ics = array_merge(...$ics); + + Search::$_search_path = []; + + $class = new \ReflectionClass('\Shimmie2\Search'); + $build_search_querylet = $class->getMethod("build_search_querylet"); + $build_search_querylet->setAccessible(true); // Use this if you are running PHP older than 8.1.0 + + $obj = new Search(); + $querylet = $build_search_querylet->invokeArgs($obj, [$tcs, $ics, $order, $limit, $start]); + + $results = $database->get_all($querylet->sql, $querylet->variables); + + static::assertThat( + [ + "res" => array_map(fn ($row) => $row['id'], $results), + "path" => Search::$_search_path, + ], + new IsEqual([ + "res" => $res, + "path" => $path ?? Search::$_search_path, + ]) + ); + } + + /* * * * * * * * * * * + * No-tag search * + * * * * * * * * * * */ + #[Depends('testUpload')] + public function testBSQ_NoTags($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: [], + res: [$image_ids[1], $image_ids[0]], + path: ["no_tags"], + ); + } + + /* * * * * * * * * * * + * Fast-path search * + * * * * * * * * * * */ + #[Depends('testUpload')] + public function testBSQ_FastPath_NoResults($image_ids) + { + $this->testUpload(); + $this->assert_BSQ( + tcs: ["maumaumau"], + res: [], + path: ["fast", "invalid_tag"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_FastPath_OneResult($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["pbx"], + res: [$image_ids[0]], + path: ["fast"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_FastPath_ManyResults($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["computer"], + res: [$image_ids[1], $image_ids[0]], + path: ["fast"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_FastPath_WildNoResults($image_ids) + { + $this->testUpload(); + $this->assert_BSQ( + tcs: ["asdfasdf*"], + res: [], + path: ["fast", "invalid_tag"], + ); + } + + /** + * Only the first image matches both the wildcard and the tag. + * This checks for a bug where searching for "a* b" would return + * an image tagged "a1 a2" because the number of matched tags + * was equal to the number of searched tags. + * + * https://github.com/shish/shimmie2/issues/547 + */ + #[Depends('testUpload')] + public function testBSQ_FastPath_WildOneResult($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["screen*"], + res: [$image_ids[0]], + path: ["fast"], + ); + } + + /** + * Test that the fast path doesn't return duplicate results + * when a wildcard matches one image multiple times. + */ + #[Depends('testUpload')] + public function testBSQ_FastPath_WildManyResults($image_ids) + { + $image_ids = $this->testUpload(); + // two images match comp* - one matches it once, one matches it twice + $this->assert_BSQ( + tcs: ["comp*"], + res: [$image_ids[1], $image_ids[0]], + path: ["fast"], + ); + } + + /* * * * * * * * * * * + * General search * + * * * * * * * * * * */ + #[Depends('testUpload')] + public function testBSQ_GeneralPath_NoResults($image_ids) + { + $this->testUpload(); + # multiple tags, one of which doesn't exist + # (test the "one tag doesn't exist = no hits" path) + $this->assert_BSQ( + tcs: ["computer", "not_a_tag"], + res: [], + path: ["general", "invalid_tag"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_GeneralPath_OneResult($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["computer", "screenshot"], + res: [$image_ids[0]], + path: ["general", "some_positives"], + ); + } + + /** + * Only the first image matches both the wildcard and the tag. + * This checks for a bug where searching for "a* b" would return + * an image tagged "a1 a2" because the number of matched tags + * was equal to the number of searched tags. + * + * https://github.com/shish/shimmie2/issues/547 + */ + #[Depends('testUpload')] + public function testBSQ_GeneralPath_WildOneResult($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["comp*", "screenshot"], + res: [$image_ids[0]], + path: ["general", "some_positives"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_GeneralPath_ManyResults($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["computer", "thing"], + res: [$image_ids[1], $image_ids[0]], + path: ["general", "some_positives"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_GeneralPath_WildManyResults($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["comp*", "-asdf"], + res: [$image_ids[1], $image_ids[0]], + path: ["general", "some_positives"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_GeneralPath_SubtractValidFromResults($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["computer", "-pbx"], + res: [$image_ids[1]], + path: ["general", "some_positives"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_GeneralPath_SubtractNotValidFromResults($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + tcs: ["computer", "-not_a_tag"], + res: [$image_ids[1], $image_ids[0]], + path: ["general", "some_positives"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_GeneralPath_SubtractValidFromDefault($image_ids) + { + $image_ids = $this->testUpload(); + // negative tag alone, should remove the image with that tag + $this->assert_BSQ( + tcs: ["-pbx"], + res: [$image_ids[1]], + path: ["general", "only_negative_tags"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_GeneralPath_SubtractNotValidFromDefault($image_ids) + { + $image_ids = $this->testUpload(); + // negative that doesn't exist, should return all results + $this->assert_BSQ( + tcs: ["-not_a_tag"], + res: [$image_ids[1], $image_ids[0]], + path: ["general", "all_nonexistent_negatives"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_GeneralPath_SubtractMultipleNotValidFromDefault($image_ids) + { + $image_ids = $this->testUpload(); + // multiple negative tags that don't exist, should return all results + $this->assert_BSQ( + tcs: ["-not_a_tag", "-also_not_a_tag"], + res: [$image_ids[1], $image_ids[0]], + path: ["general", "all_nonexistent_negatives"], + ); + } + + /* * * * * * * * * * * + * Meta Search * + * * * * * * * * * * */ + #[Depends('testUpload')] + public function testBSQ_ImgCond_NoResults($image_ids) + { + $this->testUpload(); + $this->assert_BSQ( + ics: ["hash=1234567890"], + res: [], + path: ["no_tags"], + ); + $this->assert_BSQ( + ics: ["ratio=42:12345"], + res: [], + path: ["no_tags"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_ImgCond_OneResult($image_ids) + { + $image_ids = $this->testUpload(); + $this->assert_BSQ( + ics: ["hash=feb01bab5698a11dd87416724c7a89e3"], + res: [$image_ids[0]], + path: ["no_tags"], + ); + $this->assert_BSQ( + ics: ["id={$image_ids[1]}"], + res: [$image_ids[1]], + path: ["no_tags"], + ); + $this->assert_BSQ( + ics: ["filename=screenshot"], + res: [$image_ids[0]], + path: ["no_tags"], + ); + } + + #[Depends('testUpload')] + public function testBSQ_ImgCond_ManyResults($image_ids) + { + $image_ids = $this->testUpload(); + + $this->assert_BSQ( + ics: ["size=640x480"], + res: [$image_ids[1], $image_ids[0]], + path: ["no_tags"], + ); + $this->assert_BSQ( + ics: ["tags=5"], + res: [$image_ids[1], $image_ids[0]], + path: ["no_tags"], + ); + $this->assert_BSQ( + ics: ["ext=jpg"], + res: [$image_ids[1], $image_ids[0]], + path: ["no_tags"], + ); + } + + /* * * * * * * * * * * + * Mixed * + * * * * * * * * * * */ + #[Depends('testUpload')] + public function testBSQ_TagCondWithImgCond($image_ids) + { + $image_ids = $this->testUpload(); + // multiple tags, many results + $this->assert_BSQ( + tcs: ["computer"], + ics: ["size=640x480"], + res: [$image_ids[1], $image_ids[0]], + path: ["general", "some_positives"], + ); + } +} diff --git a/ext/artists/main.php b/ext/artists/main.php index 51be763be8..05eba47ca7 100644 --- a/ext/artists/main.php +++ b/ext/artists/main.php @@ -209,7 +209,7 @@ public function onPageRequest(PageRequestEvent $event) $userIsLogged = !$user->is_anonymous(); $userIsAdmin = $user->can(Permissions::ARTISTS_ADMIN); - $images = Image::find_images(limit: 4, tags: Tag::explode($artist['name'])); + $images = Search::find_images(limit: 4, tags: Tag::explode($artist['name'])); $this->theme->show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin); /* diff --git a/ext/bulk_actions/main.php b/ext/bulk_actions/main.php index ee235eed87..e296893dc6 100644 --- a/ext/bulk_actions/main.php +++ b/ext/bulk_actions/main.php @@ -214,7 +214,7 @@ private function yield_items(array $data): \Generator private function yield_search_results(string $query): \Generator { $tags = Tag::explode($query); - return Image::find_images_iterable(0, null, $tags); + return Search::find_images_iterable(0, null, $tags); } private function sort_blocks($a, $b) diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php index 569de860c5..1104f04f3f 100644 --- a/ext/danbooru_api/main.php +++ b/ext/danbooru_api/main.php @@ -199,8 +199,8 @@ private function api_find_posts(): HTMLElement $tags = array_filter($tags, static function ($element) { return $element !== "*"; }); - $count = Image::count_images($tags); - $results = Image::find_images(max($start, 0), min($limit, 100), $tags); + $count = Search::count_images($tags); + $results = Search::find_images(max($start, 0), min($limit, 100), $tags); } // Now we have the array $results filled with Image objects diff --git a/ext/favorites/main.php b/ext/favorites/main.php index af2db5bc2b..75e3584a5b 100644 --- a/ext/favorites/main.php +++ b/ext/favorites/main.php @@ -72,7 +72,7 @@ public function onPageRequest(PageRequestEvent $event) public function onUserPageBuilding(UserPageBuildingEvent $event) { - $i_favorites_count = Image::count_images(["favorited_by={$event->display_user->name}"]); + $i_favorites_count = Search::count_images(["favorited_by={$event->display_user->name}"]); $i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1; $h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old)); $favorites_link = search_link(["favorited_by={$event->display_user->name}"]); diff --git a/ext/home/main.php b/ext/home/main.php index aa214ddff5..081eb05095 100644 --- a/ext/home/main.php +++ b/ext/home/main.php @@ -55,7 +55,7 @@ private function get_body(): string $num_comma = ""; $counter_text = ""; if ($counter_dir != 'none') { - $total = Image::count_images(); + $total = Search::count_images(); $num_comma = number_format($total); if ($counter_dir != 'text-only') { diff --git a/ext/image/main.php b/ext/image/main.php index 76cac86154..16c846e75e 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -263,7 +263,7 @@ public function onImageReplace(ImageReplaceEvent $event) public function onUserPageBuilding(UserPageBuildingEvent $event) { $u_name = url_escape($event->display_user->name); - $i_image_count = Image::count_images(["user={$event->display_user->name}"]); + $i_image_count = Search::count_images(["user={$event->display_user->name}"]); $i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1; $h_image_rate = sprintf("%.1f", ($i_image_count / $i_days_old)); $images_link = search_link(["user=$u_name"]); diff --git a/ext/index/main.php b/ext/index/main.php index d8c57900aa..ea776f227c 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -22,7 +22,7 @@ public function onInitExt(InitExtEvent $event) public function onPageRequest(PageRequestEvent $event) { - global $cache, $page, $user; + global $cache, $config, $page, $user; if ($event->page_matches("post/list")) { if (isset($_GET['search'])) { $page->set_mode(PageMode::REDIRECT); @@ -67,7 +67,7 @@ public function onPageRequest(PageRequestEvent $event) return; } - $total_pages = Image::count_pages($search_terms); + $total_pages = (int)ceil(Search::count_images($search_terms) / $config->get_int(IndexConfig::IMAGES)); $images = []; if (SPEED_HAX && $total_pages > $fast_page_limit && !$user->can("big_search")) { @@ -79,14 +79,14 @@ public function onPageRequest(PageRequestEvent $event) // extra caching for the first few post/list pages $images = $cache->get("post-list:$page_number"); if (is_null($images)) { - $images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); + $images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); $cache->set("post-list:$page_number", $images, 60); } } } if (!$images) { - $images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); + $images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); } } catch (PermissionDeniedException $pde) { $this->theme->display_error(403, "Permission denied", $pde->error); @@ -156,7 +156,7 @@ public function onCommand(CommandEvent $event) } if ($event->cmd == "search") { $query = count($event->args) > 0 ? Tag::explode($event->args[0]) : []; - $items = Image::find_images(limit: 1000, tags: $query); + $items = Search::find_images(limit: 1000, tags: $query); foreach ($items as $item) { print("{$item->hash}\n"); } diff --git a/ext/index/test.php b/ext/index/test.php index 127b306aca..ab6d16f9b7 100644 --- a/ext/index/test.php +++ b/ext/index/test.php @@ -49,191 +49,6 @@ public function testIndexPage() $this->assert_response(200); } - public function testWeirdTags() - { - $this->log_in_as_user(); - $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "question? colon:thing exclamation!"); - $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "question. colon_thing exclamation%"); - - $this->assert_search_results(["question?"], [$image_id_1]); - $this->assert_search_results(["question."], [$image_id_2]); - $this->assert_search_results(["colon:thing"], [$image_id_1]); - $this->assert_search_results(["colon_thing"], [$image_id_2]); - $this->assert_search_results(["exclamation!"], [$image_id_1]); - $this->assert_search_results(["exclamation%"], [$image_id_2]); - } - - // base case - public function testUpload(): array - { - $this->log_in_as_user(); - $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "thing computer screenshot pbx phone"); - $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "thing computer computing bedroom workshop"); - $this->log_out(); - - # make sure both uploads were ok - $this->assertTrue($image_id_1 > 0); - $this->assertTrue($image_id_2 > 0); - - return [$image_id_1, $image_id_2]; - } - - /* * * * * * * * * * * - * Tag Search * - * * * * * * * * * * */ - #[Depends('testUpload')] - public function testTagSearchNoResults($image_ids) - { - $this->testUpload(); - $this->assert_search_results(["maumaumau"], []); - } - - #[Depends('testUpload')] - public function testTagSearchOneResult($image_ids) - { - $image_ids = $this->testUpload(); - $this->assert_search_results(["pbx"], [$image_ids[0]]); - } - - #[Depends('testUpload')] - public function testTagSearchManyResults($image_ids) - { - $image_ids = $this->testUpload(); - $this->assert_search_results(["computer"], [$image_ids[1], $image_ids[0]]); - } - - /* * * * * * * * * * * - * Multi-Tag Search * - * * * * * * * * * * */ - #[Depends('testUpload')] - public function testMultiTagSearchNoResults($image_ids) - { - $this->testUpload(); - # multiple tags, one of which doesn't exist - # (test the "one tag doesn't exist = no hits" path) - $this->assert_search_results(["computer", "asdfasdfwaffle"], []); - } - - #[Depends('testUpload')] - public function testMultiTagSearchOneResult($image_ids) - { - $image_ids = $this->testUpload(); - $this->assert_search_results(["computer", "screenshot"], [$image_ids[0]]); - } - - #[Depends('testUpload')] - public function testMultiTagSearchManyResults($image_ids) - { - $image_ids = $this->testUpload(); - $this->assert_search_results(["computer", "thing"], [$image_ids[1], $image_ids[0]]); - } - - /* * * * * * * * * * * - * Meta Search * - * * * * * * * * * * */ - #[Depends('testUpload')] - public function testMetaSearchNoResults($image_ids) - { - $this->testUpload(); - $this->assert_search_results(["hash=1234567890"], []); - $this->assert_search_results(["ratio=42:12345"], []); - } - - #[Depends('testUpload')] - public function testMetaSearchOneResult($image_ids) - { - $image_ids = $this->testUpload(); - $this->assert_search_results(["hash=feb01bab5698a11dd87416724c7a89e3"], [$image_ids[0]]); - $this->assert_search_results(["md5=feb01bab5698a11dd87416724c7a89e3"], [$image_ids[0]]); - $this->assert_search_results(["id={$image_ids[1]}"], [$image_ids[1]]); - $this->assert_search_results(["filename=screenshot"], [$image_ids[0]]); - } - - #[Depends('testUpload')] - public function testMetaSearchManyResults($image_ids) - { - $image_ids = $this->testUpload(); - $this->assert_search_results(["size=640x480"], [$image_ids[1], $image_ids[0]]); - $this->assert_search_results(["tags=5"], [$image_ids[1], $image_ids[0]]); - $this->assert_search_results(["ext=jpg"], [$image_ids[1], $image_ids[0]]); - } - - /* * * * * * * * * * * - * Wildcards * - * * * * * * * * * * */ - #[Depends('testUpload')] - public function testWildSearchNoResults($image_ids) - { - $this->testUpload(); - $this->assert_search_results(["asdfasdf*"], []); - } - - #[Depends('testUpload')] - public function testWildSearchOneResult($image_ids) - { - $image_ids = $this->testUpload(); - // Only the first image matches both the wildcard and the tag. - // This checks for https://github.com/shish/shimmie2/issues/547 - $this->assert_search_results(["comp*", "screenshot"], [$image_ids[0]]); - } - - #[Depends('testUpload')] - public function testWildSearchManyResultsSimple($image_ids) - { - $image_ids = $this->testUpload(); - // two images match comp* - one matches it once, one matches it twice - $this->assert_search_results(["comp*"], [$image_ids[1], $image_ids[0]]); - } - - #[Depends('testUpload')] - public function testWildSearchManyResultsComplex($image_ids) - { - $image_ids = $this->testUpload(); - // same thing, but with the complex branch - $this->assert_search_results(["comp*", "-asdf"], [$image_ids[1], $image_ids[0]]); - } - - /* * * * * * * * * * * - * Mixed * - * * * * * * * * * * */ - #[Depends('testUpload')] - public function testMixedSearchTagMeta($image_ids) - { - $image_ids = $this->testUpload(); - // multiple tags, many results - $this->assert_search_results(["computer", "size=640x480"], [$image_ids[1], $image_ids[0]]); - } - - /* * * * * * * * * * * - * Negative * - * * * * * * * * * * */ - #[Depends('testUpload')] - public function testSubtractFromSearch($image_ids) - { - $image_ids = $this->testUpload(); - - // negative tag, should have one result - $this->assert_search_results(["computer", "-pbx"], [$image_ids[1]]); - - // removing something that doesn't exist should have no effect - $this->assert_search_results(["computer", "-not_a_tag"], [$image_ids[1], $image_ids[0]]); - } - - #[Depends('testUpload')] - public function testSubtractFromDefault($image_ids) - { - $image_ids = $this->testUpload(); - - // negative tag alone, should work - $this->assert_search_results(["-pbx"], [$image_ids[1]]); - - // negative that doesn't exist - $this->assert_search_results(["-not_a_tag"], [$image_ids[1], $image_ids[0]]); - - // multiple negative tags that don't exist - $this->assert_search_results(["-not_a_tag", "-also_not_a_tag"], [$image_ids[1], $image_ids[0]]); - } - // This isn't really an index thing, we just want to test this from // SOMEWHERE because the default theme doesn't use them. public function test_nav() diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php index a42a25f692..61b0b0c305 100644 --- a/ext/numeric_score/main.php +++ b/ext/numeric_score/main.php @@ -119,9 +119,9 @@ public function onUserPageBuilding(UserPageBuildingEvent $event) $this->theme->get_nuller($event->display_user); } - $n_up = Image::count_images(["upvoted_by={$event->display_user->name}"]); + $n_up = Search::count_images(["upvoted_by={$event->display_user->name}"]); $link_up = search_link(["upvoted_by={$event->display_user->name}"]); - $n_down = Image::count_images(["downvoted_by={$event->display_user->name}"]); + $n_down = Search::count_images(["downvoted_by={$event->display_user->name}"]); $link_down = search_link(["downvoted_by={$event->display_user->name}]"]); $event->add_stats("$n_up Upvotes / $n_down Downvotes"); } diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index 29b78993b7..70e9758e3d 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -420,7 +420,7 @@ protected function postShow(int $id = null) protected function postIndex(int $limit, int $page, array $tags) { $start = ($page - 1) * $limit; - $results = Image::find_images(max($start, 0), min($limit, 100), $tags); + $results = Search::find_images(max($start, 0), min($limit, 100), $tags); $posts = []; foreach ($results as $img) { if (!is_object($img)) { diff --git a/ext/pools/main.php b/ext/pools/main.php index 226949564b..e9f38cc6c0 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -372,7 +372,7 @@ public function onPageRequest(PageRequestEvent $event) break; case "import": if ($this->have_permission($user, $pool)) { - $images = Image::find_images( + $images = Search::find_images( limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000), tags: Tag::explode($_POST["pool_tag"]) ); diff --git a/ext/rating/main.php b/ext/rating/main.php index b2db0f5f47..9ad7741529 100644 --- a/ext/rating/main.php +++ b/ext/rating/main.php @@ -376,7 +376,7 @@ public function onPageRequest(PageRequestEvent $event) } else { $n = 0; while (true) { - $images = Image::find_images($n, 100, Tag::explode($_POST["query"])); + $images = Search::find_images($n, 100, Tag::explode($_POST["query"])); if (count($images) == 0) { break; } diff --git a/ext/regen_thumb/main.php b/ext/regen_thumb/main.php index 227d23cdb1..b5352cd14b 100644 --- a/ext/regen_thumb/main.php +++ b/ext/regen_thumb/main.php @@ -30,7 +30,7 @@ public function onPageRequest(PageRequestEvent $event) } if ($event->page_matches("regen_thumb/mass") && $user->can(Permissions::DELETE_IMAGE) && isset($_POST['tags'])) { $tags = Tag::explode(strtolower($_POST['tags']), false); - $images = Image::find_images(limit: 10000, tags: $tags); + $images = Search::find_images(limit: 10000, tags: $tags); foreach ($images as $image) { $this->regenerate_thumbnail($image); diff --git a/ext/rss_images/main.php b/ext/rss_images/main.php index f1e0158356..2bc29c1854 100644 --- a/ext/rss_images/main.php +++ b/ext/rss_images/main.php @@ -31,7 +31,7 @@ public function onPageRequest(PageRequestEvent $event) return; } try { - $images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); + $images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); $this->do_rss($images, $search_terms, $page_number); } catch (SearchTermParseException $stpe) { $this->theme->display_error(400, "Search parse error", $stpe->error); diff --git a/ext/rule34/main.php b/ext/rule34/main.php index 2ec39607c6..ee3da462b7 100644 --- a/ext/rule34/main.php +++ b/ext/rule34/main.php @@ -84,7 +84,7 @@ public function onCommand(CommandEvent $event) { global $cache; if ($event->cmd == "wipe-thumb-cache") { - foreach (Image::find_images_iterable(0, null, Tag::explode($event->args[0])) as $image) { + foreach (Search::find_images_iterable(0, null, Tag::explode($event->args[0])) as $image) { print($image->id . "\n"); $cache->delete("thumb-block:{$image->id}"); } diff --git a/ext/shimmie_api/main.php b/ext/shimmie_api/main.php index cf590df2e2..9fe638eeda 100644 --- a/ext/shimmie_api/main.php +++ b/ext/shimmie_api/main.php @@ -70,7 +70,7 @@ public function onPageRequest(PageRequestEvent $event) $search_terms = $event->get_search_terms(); $page_number = $event->get_page_number(); $page_size = $event->get_page_size(); - $images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); + $images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); $safe_images = []; foreach ($images as $image) { $image->get_tag_array(); @@ -131,7 +131,7 @@ private function api_get_user(string $type, string $query): array for ($i = 0; $i < 4; $i++) { unset($all[$i]); } - $all['uploadcount'] = Image::count_images(["user_id=" . $all['id']]); + $all['uploadcount'] = Search::count_images(["user_id=" . $all['id']]); $all['commentcount'] = $database->get_one( "SELECT COUNT(*) AS count FROM comments WHERE owner_id=:owner_id", ["owner_id" => $all['id']] diff --git a/ext/sitemap/main.php b/ext/sitemap/main.php index c3fefe18a7..0d08142b4d 100644 --- a/ext/sitemap/main.php +++ b/ext/sitemap/main.php @@ -42,7 +42,7 @@ public function onSetupBuilding(SetupBuildingEvent $event) private function handle_smaller_sitemap() { /* --- Add latest images to sitemap with higher priority --- */ - $latestimages = Image::find_images(limit: 50); + $latestimages = Search::find_images(limit: 50); if (empty($latestimages)) { return; } @@ -85,7 +85,7 @@ private function handle_full_sitemap() $this->add_sitemap_queue($popular_tags, "monthly", "0.9" /* not sure how to deal with date here */); /* --- Add latest images to sitemap with higher priority --- */ - $latestimages = Image::find_images(limit: 50); + $latestimages = Search::find_images(limit: 50); $latestimages_urllist = []; $latest_image = null; foreach ($latestimages as $arrayid => $image) { @@ -107,7 +107,7 @@ private function handle_full_sitemap() $this->add_sitemap_queue($other_tags, "monthly", "0.7" /* not sure how to deal with date here */); /* --- Add all other images to sitemap with lower priority --- */ - $otherimages = Image::find_images(offset: 51, limit: 10000000); + $otherimages = Search::find_images(offset: 51, limit: 10000000); $image = null; foreach ($otherimages as $arrayid => $image) { // create url from image id's diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php index 47b0f13cce..3e49ee27bd 100644 --- a/ext/tag_edit/main.php +++ b/ext/tag_edit/main.php @@ -300,7 +300,7 @@ private function mass_tag_edit(string $search, string $replace, bool $commit) log_info("tag_edit", "Mass editing tags: '$search' -> '$replace'"); if (count($search_set) == 1 && count($replace_set) == 1) { - $images = Image::find_images(limit: 10, tags: $replace_set); + $images = Search::find_images(limit: 10, tags: $replace_set); if (count($images) == 0) { log_info("tag_edit", "No images found with target tag, doing in-place rename"); $database->execute( @@ -329,7 +329,7 @@ private function mass_tag_edit(string $search, string $replace, bool $commit) $search_forward[] = "id<$last_id"; } - $images = Image::find_images(limit: 100, tags: $search_forward); + $images = Search::find_images(limit: 100, tags: $search_forward); if (count($images) == 0) { break; } @@ -364,7 +364,7 @@ private function mass_source_edit(string $tags, string $source) $search_forward[] = "id<$last_id"; } - $images = Image::find_images(limit: 100, tags: $search_forward); + $images = Search::find_images(limit: 100, tags: $search_forward); if (count($images) == 0) { break; } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 7158383226..808539b4f4 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -67,8 +67,12 @@ public function setUp(): void $_tracer->begin($this->name()); $_tracer->begin("setUp"); $class = str_replace("Test", "", get_class($this)); - if (!ExtensionInfo::get_for_extension_class($class)->is_supported()) { - $this->markTestSkipped("$class not supported with this database"); + try { + if (!ExtensionInfo::get_for_extension_class($class)->is_supported()) { + $this->markTestSkipped("$class not supported with this database"); + } + } catch (ExtensionNotFound $e) { + // ignore - this is a core test rather than an extension test } // Set up a clean environment for each test @@ -220,7 +224,7 @@ protected function assert_no_content(string $content): void protected function assert_search_results($tags, $results): void { - $images = Image::find_images(0, null, $tags); + $images = Search::find_images(0, null, $tags); $ids = []; foreach ($images as $image) { $ids[] = $image->id; From 672ef4ac5309e1bdc9b991eff458bf00122c1a92 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 17:10:09 +0000 Subject: [PATCH 006/154] php8.3 is stable now --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 369765aa92..f6104ff8be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,7 +57,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.1', '8.2'] + php: ['8.1', '8.2', '8.3'] database: ['pgsql', 'mysql', 'sqlite'] runs-on: ubuntu-latest From 7f98412d8be7e697741edf08cc4f9a3100720864 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 17:06:54 +0000 Subject: [PATCH 007/154] cache_get_or_set function --- core/config.php | 32 ++++++++++++++++---------------- core/imageboard/search.php | 9 ++------- core/imageboard/tag.php | 12 ++++++------ core/polyfills.php | 15 +++++++++++++++ ext/autocomplete/main.php | 14 ++++---------- ext/blocks/main.php | 6 +----- ext/comment/main.php | 21 +++++++-------------- ext/featured/main.php | 19 +++++++++++-------- ext/index/main.php | 10 +++++----- ext/pm/main.php | 23 +++++++++++------------ ext/report_image/main.php | 14 ++++++-------- ext/tag_list/main.php | 1 - 12 files changed, 84 insertions(+), 92 deletions(-) diff --git a/core/config.php b/core/config.php index 85c3a2a1eb..341ed09a04 100644 --- a/core/config.php +++ b/core/config.php @@ -278,27 +278,27 @@ public function __construct( $this->table_name = $table_name; $this->sub_value = $sub_value; $this->sub_column = $sub_column; - $this->cache_name = empty($sub_value) ? "config" : "config_{$sub_value}"; + $this->cache_name = empty($sub_value) ? "config" : "config_{$sub_column}={$sub_value}"; + $this->values = cache_get_or_set($this->cache_name, fn () => $this->get_values()); + } - $cached = $cache->get($this->cache_name); - if (!is_null($cached)) { - $this->values = $cached; - } else { - $this->values = []; + private function get_values(): mixed + { + $values = []; - $query = "SELECT name, value FROM {$this->table_name}"; - $args = []; + $query = "SELECT name, value FROM {$this->table_name}"; + $args = []; - if (!empty($sub_column) && !empty($sub_value)) { - $query .= " WHERE $sub_column = :sub_value"; - $args["sub_value"] = $sub_value; - } + if (!empty($this->sub_column) && !empty($this->sub_value)) { + $query .= " WHERE {$this->sub_column} = :sub_value"; + $args["sub_value"] = $this->sub_value; + } - foreach ($this->database->get_all($query, $args) as $row) { - $this->values[$row["name"]] = $row["value"]; - } - $cache->set($this->cache_name, $this->values); + foreach ($this->database->get_all($query, $args) as $row) { + $values[$row["name"]] = $row["value"]; } + + return $values; } public function save(string $name = null): void diff --git a/core/imageboard/search.php b/core/imageboard/search.php index d6af5815eb..2b9dfff577 100644 --- a/core/imageboard/search.php +++ b/core/imageboard/search.php @@ -119,13 +119,8 @@ public static function count_tag(string $tag): int private static function count_total_images(): int { - global $cache, $database; - $total = $cache->get("image-count"); - if (is_null($total)) { - $total = (int)$database->get_one("SELECT COUNT(*) FROM images"); - $cache->set("image-count", $total, 600); - } - return $total; + global $database; + return cache_get_or_set("image-count", fn () => (int)$database->get_one("SELECT COUNT(*) FROM images"), 600); } /** diff --git a/core/imageboard/tag.php b/core/imageboard/tag.php index 547ed0f040..46bfc07071 100644 --- a/core/imageboard/tag.php +++ b/core/imageboard/tag.php @@ -55,9 +55,9 @@ public static function tags(string $search, int $limit = 10): array $cache_key .= "-" . $limit; } - $res = $cache->get($cache_key); - if (is_null($res)) { - $res = $database->get_pairs( + $res = cache_get_or_set( + $cache_key, + fn () => $database->get_pairs( " SELECT tag, count FROM tags @@ -68,9 +68,9 @@ public static function tags(string $search, int $limit = 10): array $limitSQL ", $SQLarr - ); - $cache->set($cache_key, $res, 600); - } + ), + 600 + ); $counts = []; foreach ($res as $k => $v) { diff --git a/core/polyfills.php b/core/polyfills.php index bcecda02b8..6609945d8b 100644 --- a/core/polyfills.php +++ b/core/polyfills.php @@ -814,3 +814,18 @@ function stringer($s): string } return ""; } + +/** + * If a value is in the cache, return it; otherwise, call the callback + * to generate it and store it in the cache. + */ +function cache_get_or_set(string $key, callable $callback, int $ttl = 0) +{ + global $cache; + $value = $cache->get($key); + if ($value === null) { + $value = $callback(); + $cache->set($key, $value, $ttl); + } + return $value; +} diff --git a/ext/autocomplete/main.php b/ext/autocomplete/main.php index a350c00d96..147260e3d0 100644 --- a/ext/autocomplete/main.php +++ b/ext/autocomplete/main.php @@ -62,10 +62,8 @@ private function complete(string $search, int $limit): array $cache_key .= "-" . $limit; } - $res = $cache->get($cache_key); - if (is_null($res)) { - $res = $database->get_pairs( - " + return cache_get_or_set($cache_key, fn () => $database->get_pairs( + " SELECT tag, count FROM tags WHERE LOWER(tag) LIKE LOWER(:search) @@ -74,11 +72,7 @@ private function complete(string $search, int $limit): array ORDER BY count DESC $limitSQL ", - $SQLarr - ); - $cache->set($cache_key, $res, 600); - } - - return $res; + $SQLarr + ), 600); } } diff --git a/ext/blocks/main.php b/ext/blocks/main.php index c7bcb2b969..2711fd9aba 100644 --- a/ext/blocks/main.php +++ b/ext/blocks/main.php @@ -53,11 +53,7 @@ public function onPageRequest(PageRequestEvent $event) { global $cache, $database, $page, $user; - $blocks = $cache->get("blocks"); - if (is_null($blocks)) { - $blocks = $database->get_all("SELECT * FROM blocks"); - $cache->set("blocks", $blocks, 600); - } + $blocks = cache_get_or_set("blocks", fn () => $database->get_all("SELECT * FROM blocks"), 600); foreach ($blocks as $block) { $path = implode("/", $event->args); if (strlen($path) < 4000 && fnmatch($block['pages'], $path)) { diff --git a/ext/comment/main.php b/ext/comment/main.php index a16d8d6435..8b5c4874ed 100644 --- a/ext/comment/main.php +++ b/ext/comment/main.php @@ -283,20 +283,17 @@ private function onPageRequest_list(PageRequestEvent $event) { global $cache, $config, $database, $user; + $threads_per_page = 10; + $where = SPEED_HAX ? "WHERE posted > now() - interval '24 hours'" : ""; - $total_pages = $cache->get("comment_pages"); - if (is_null($total_pages)) { - $total_pages = (int)ceil($database->get_one(" - SELECT COUNT(c1) - FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1 - ") / 10); - $cache->set("comment_pages", $total_pages, 600); - } + $total_pages = cache_get_or_set("comment_pages", fn () => (int)ceil($database->get_one(" + SELECT COUNT(c1) + FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1 + ") / $threads_per_page), 600); $total_pages = max($total_pages, 1); $current_page = $event->try_page_num(1, $total_pages); - $threads_per_page = 10; $start = $threads_per_page * $current_page; $result = $database->execute(" @@ -357,11 +354,7 @@ public function onPostListBuilding(PostListBuildingEvent $event) global $cache, $config; $cc = $config->get_int("comment_count"); if ($cc > 0) { - $recent = $cache->get("recent_comments"); - if (is_null($recent)) { - $recent = $this->get_recent_comments($cc); - $cache->set("recent_comments", $recent, 60); - } + $recent = cache_get_or_set("recent_comments", fn () => $this->get_recent_comments($cc), 60); if (count($recent) > 0) { $this->theme->display_recent_comments($recent); } diff --git a/ext/featured/main.php b/ext/featured/main.php index 11cba6ecb0..de82bde3f4 100644 --- a/ext/featured/main.php +++ b/ext/featured/main.php @@ -52,14 +52,17 @@ public function onPostListBuilding(PostListBuildingEvent $event) global $cache, $config, $page, $user; $fid = $config->get_int("featured_id"); if ($fid > 0) { - $image = $cache->get("featured_image_object:$fid"); - if (is_null($image)) { - $image = Image::by_id($fid); - if ($image) { // make sure the object is fully populated before saving - $image->get_tag_array(); - } - $cache->set("featured_image_object:$fid", $image, 600); - } + $image = cache_get_or_set( + "featured_image_object:$fid", + function () use ($fid) { + $image = Image::by_id($fid); + if ($image) { // make sure the object is fully populated before saving + $image->get_tag_array(); + } + return $image; + }, + 600 + ); if (!is_null($image)) { if (Extension::is_enabled(RatingsInfo::KEY)) { if (!in_array($image->rating, Ratings::get_user_class_privs($user))) { diff --git a/ext/index/main.php b/ext/index/main.php index ea776f227c..9bfd6bfe9e 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -77,11 +77,11 @@ public function onPageRequest(PageRequestEvent $event) if (SPEED_HAX) { if ($count_search_terms === 0 && ($page_number < 10)) { // extra caching for the first few post/list pages - $images = $cache->get("post-list:$page_number"); - if (is_null($images)) { - $images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms); - $cache->set("post-list:$page_number", $images, 60); - } + $images = cache_get_or_set( + "post-list:$page_number", + fn () => Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms), + 60 + ); } } diff --git a/ext/pm/main.php b/ext/pm/main.php index 5600a2d94b..6f56761bcd 100644 --- a/ext/pm/main.php +++ b/ext/pm/main.php @@ -297,18 +297,17 @@ public function onSendPM(SendPMEvent $event) private function count_pms(User $user) { - global $cache, $database; + global $database; - $count = $cache->get("pm-count:{$user->id}"); - if (is_null($count)) { - $count = $database->get_one(" - SELECT count(*) - FROM private_message - WHERE to_id = :to_id - AND is_read = :is_read - ", ["to_id" => $user->id, "is_read" => false]); - $cache->set("pm-count:{$user->id}", $count, 600); - } - return $count; + return cache_get_or_set( + "pm-count:{$user->id}", + fn () => $database->get_one(" + SELECT count(*) + FROM private_message + WHERE to_id = :to_id + AND is_read = :is_read + ", ["to_id" => $user->id, "is_read" => false]), + 600 + ); } } diff --git a/ext/report_image/main.php b/ext/report_image/main.php index 2cc5b4be45..4a348a7f13 100644 --- a/ext/report_image/main.php +++ b/ext/report_image/main.php @@ -240,14 +240,12 @@ public function get_reported_images(): array public function count_reported_images(): int { - global $cache, $database; - - $count = $cache->get("image-report-count"); - if (is_null($count)) { - $count = $database->get_one("SELECT count(*) FROM image_reports"); - $cache->set("image-report-count", $count, 600); - } + global $database; - return (int)$count; + return (int)cache_get_or_set( + "image-report-count", + fn () => $database->get_one("SELECT count(*) FROM image_reports"), + 600 + ); } } diff --git a/ext/tag_list/main.php b/ext/tag_list/main.php index 3d13c7ae93..89d451389b 100644 --- a/ext/tag_list/main.php +++ b/ext/tag_list/main.php @@ -553,7 +553,6 @@ public static function get_related_tags(array $search, int $limit): array { global $cache, $database; - $wild_tags = $search; $cache_key = "related_tags:" . md5(Tag::implode($search)); $related_tags = $cache->get($cache_key); From d8a7ac985d9b7cd01216d755a377ea5f2de4c911 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 17:21:41 +0000 Subject: [PATCH 008/154] make cache table name into a valid postgres identifier for notifications --- core/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/config.php b/core/config.php index 341ed09a04..35f068695b 100644 --- a/core/config.php +++ b/core/config.php @@ -278,7 +278,7 @@ public function __construct( $this->table_name = $table_name; $this->sub_value = $sub_value; $this->sub_column = $sub_column; - $this->cache_name = empty($sub_value) ? "config" : "config_{$sub_column}={$sub_value}"; + $this->cache_name = empty($sub_value) ? "config" : "config_{$sub_column}_{$sub_value}"; $this->values = cache_get_or_set($this->cache_name, fn () => $this->get_values()); } From 19c3953519c2b26f1986e18eebeca69cb34a23c4 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 22:08:31 +0000 Subject: [PATCH 009/154] ai-generated border color --- themes/rule34v2/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/themes/rule34v2/style.css b/themes/rule34v2/style.css index 4b60f8b985..94481eb7ce 100644 --- a/themes/rule34v2/style.css +++ b/themes/rule34v2/style.css @@ -257,6 +257,7 @@ ul.tagit li.tagit-new { } +[data-tags~="ai-generated"]>A>IMG { background: #BC8F8F; } [data-tags~="animated"]>A>IMG { background: #CC00CC; } [data-ext="mp4"]>A>IMG, [data-ext="webm"]>A>IMG { background: #0000FF; } From 98a199645a72616e497096814d51a0987d308e6c Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 22:09:21 +0000 Subject: [PATCH 010/154] sync --- themes/rule34v2/header.inc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/themes/rule34v2/header.inc b/themes/rule34v2/header.inc index b2fde29c4c..f60c8704f1 100644 --- a/themes/rule34v2/header.inc +++ b/themes/rule34v2/header.inc @@ -68,26 +68,28 @@ -
    + From c5e96a72021106a4d23fcf4ee83309a4b20182b5 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 14 Dec 2023 22:31:53 +0000 Subject: [PATCH 011/154] functions --- themes/rule34v2/tag_edit.theme.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/themes/rule34v2/tag_edit.theme.php b/themes/rule34v2/tag_edit.theme.php index 1c1533af8e..8994ceb42d 100644 --- a/themes/rule34v2/tag_edit.theme.php +++ b/themes/rule34v2/tag_edit.theme.php @@ -21,26 +21,26 @@ public function display_mass_editor() $page->add_block(new Block("Mass Tag Edit", $html)); } - public function get_tag_editor_html(Image $image): string + public function get_tag_editor_html(Image $image): \MicroHTML\HTMLElement { $h_tags = html_escape($image->get_tag_list()); - return " + return \MicroHTML\rawHTML(" Tags - "; + "); } - public function get_source_editor_html(Image $image): string + public function get_source_editor_html(Image $image): \MicroHTML\HTMLElement { global $user; $h_source = html_escape($image->get_source()); $f_source = $this->format_source($image->get_source()); $style = "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"; - return " + return \MicroHTML\rawHTML(" Source Link @@ -52,6 +52,6 @@ public function get_source_editor_html(Image $image): string ")." - "; + "); } } From 10ba2ccb7fee68f382d2574707b4fbc62105e5a3 Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 15 Dec 2023 07:55:38 +0000 Subject: [PATCH 012/154] fix search encoding --- core/tests/UrlsTest.php | 4 ++-- core/urls.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tests/UrlsTest.php b/core/tests/UrlsTest.php index 5d939c84ae..59ab6342f4 100644 --- a/core/tests/UrlsTest.php +++ b/core/tests/UrlsTest.php @@ -13,11 +13,11 @@ class UrlsTest extends TestCase public function test_search_link() { $this->assertEquals( - "/test/post/list/bar+foo/1", + "/test/post/list/bar%20foo/1", search_link(["foo", "bar"]) ); $this->assertEquals( - "/test/post/list/cat%2A+rating%3D%5Eq/1", + "/test/post/list/cat%2A%20rating%3D%5Eq/1", search_link(["rating=?", "cat*"]) ); } diff --git a/core/urls.php b/core/urls.php index e2fbf864d2..35e6212004 100644 --- a/core/urls.php +++ b/core/urls.php @@ -30,7 +30,7 @@ public function make_link(): string function search_link(array $terms = [], int $page = 1): string { if($terms) { - $q = urlencode(Tag::caret(Tag::implode($terms))); + $q = rawurlencode(Tag::caret(Tag::implode($terms))); return make_link("post/list/$q/$page"); } else { return make_link("post/list/$page"); From c1acf5c38c11ec458e2a5f0c5f9f6fdf9c10845b Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 15 Dec 2023 13:23:20 +0000 Subject: [PATCH 013/154] remove dead import --- core/urls.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/urls.php b/core/urls.php index 35e6212004..66fc3cf8b9 100644 --- a/core/urls.php +++ b/core/urls.php @@ -4,8 +4,6 @@ namespace Shimmie2; -use PhpParser\Node\Expr\Cast\Double; - class Link { public ?string $page; From f6919cf9c92dc2fe09e1994e77d2a943cb774209 Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 15 Dec 2023 17:20:34 +0000 Subject: [PATCH 014/154] theme updates --- themes/rule34v2/tag_edit.theme.php | 45 ++++++++++++++++++++++++++++++ themes/rule34v2/view.theme.php | 44 +++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 themes/rule34v2/view.theme.php diff --git a/themes/rule34v2/tag_edit.theme.php b/themes/rule34v2/tag_edit.theme.php index 8994ceb42d..5c9b1a3856 100644 --- a/themes/rule34v2/tag_edit.theme.php +++ b/themes/rule34v2/tag_edit.theme.php @@ -4,6 +4,10 @@ namespace Shimmie2; +use MicroHTML\HTMLElement; + +use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A}; + class CustomTagEditTheme extends TagEditTheme { public function display_mass_editor() @@ -54,4 +58,45 @@ public function get_source_editor_html(Image $image): \MicroHTML\HTMLElement "); } + + public function get_user_editor_html(Image $image): HTMLElement + { + global $user; + $owner = $image->get_owner()->name; + $date = rawHTML(autodate($image->posted)); + $ip = $user->can(Permissions::VIEW_IP) ? rawHTML(" (" . show_ip($image->owner_ip, "Post posted {$image->posted}") . ")") : ""; + $info = SHM_POST_INFO( + "Uploader", + $user->can(Permissions::EDIT_IMAGE_OWNER), + emptyHTML( + A(["class" => "username", "href" => make_link("user/$owner")], $owner), + $ip, + ", ", + $date, + INPUT(["type" => "text", "name" => "tag_edit__owner", "value" => $owner]) + ), + ); + // SHM_POST_INFO returns a TR, let's sneakily append + // a TD with the avatar in it + $info->appendChild( + TD( + ["width" => "80px", "rowspan" => "4"], + rawHTML($image->get_owner()->get_avatar_html()) + ) + ); + return $info; + } + + public function get_lock_editor_html(Image $image): HTMLElement + { + global $user; + return SHM_POST_INFO( + "Locked", + $user->can(Permissions::EDIT_IMAGE_LOCK), + emptyHTML( + INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()]), + $image->is_locked() ? "Yes (Only admins may edit these details)" : "No", + ), + ); + } } diff --git a/themes/rule34v2/view.theme.php b/themes/rule34v2/view.theme.php new file mode 100644 index 0000000000..cf848c2d6d --- /dev/null +++ b/themes/rule34v2/view.theme.php @@ -0,0 +1,44 @@ +is_locked() ? "
    [Post Locked]" : ""); + } + + $html = make_form(make_link("post/set"))." + + + "; + foreach ($editor_parts as $part) { + $html .= $part; + } + if ( + (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) && + $user->can(Permissions::EDIT_IMAGE_TAG) + ) { + $html .= " + + "; + } + $html .= " +
    + +
    + + "; + return $html; + } +} From 23c3bf00fb3a44c3fed3f9057b51921e724ee2ee Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 15 Dec 2023 19:00:52 +0000 Subject: [PATCH 015/154] run server in devcontainer --- .devcontainer/devcontainer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4f6b9d4495..6acb534bf9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,10 +12,10 @@ // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], + "forwardPorts": [8000], // Uncomment the next line to run commands after the container is created. - // "postCreateCommand": "apt update && apt install -y composer php8.2-xdebug git", + "postCreateCommand": "cd /workspaces/shimmie2 && /usr/bin/php -S 0.0.0.0:8000 tests/router.php 2>&1 | grep --line-buffered -vE \" (Accepted|Closing)\"", "customizations": { "vscode": { From 3581945e0975558dd60c7c0b2a34ac415baaf2e6 Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 15 Dec 2023 19:01:15 +0000 Subject: [PATCH 016/154] [comment] check null once --- ext/comment/main.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ext/comment/main.php b/ext/comment/main.php index 8b5c4874ed..6bf5f686ba 100644 --- a/ext/comment/main.php +++ b/ext/comment/main.php @@ -565,7 +565,8 @@ public static function get_hash(): string private function is_spam_akismet(string $text): bool { global $config, $user; - if (strlen($config->get_string('comment_wordpress_key')) > 0) { + $key = $config->get_string('comment_wordpress_key'); + if (!is_null($key) && strlen($key) > 0) { $comment = [ 'author' => $user->name, 'email' => $user->email, @@ -577,11 +578,7 @@ private function is_spam_akismet(string $text): bool ]; // @phpstan-ignore-next-line - $akismet = new \Akismet( - $_SERVER['SERVER_NAME'], - $config->get_string('comment_wordpress_key'), - $comment - ); + $akismet = new \Akismet($_SERVER['SERVER_NAME'], $key, $comment); // @phpstan-ignore-next-line if ($akismet->errorsExist()) { From e4bbdc2ac688ee404ed4f06ad59e509143d04bd7 Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 15 Dec 2023 21:14:43 +0000 Subject: [PATCH 017/154] [themes/lite] remove rr --- themes/lite/comment.theme.php | 6 ++++-- themes/lite/setup.theme.php | 2 +- themes/lite/themelet.class.php | 12 ------------ themes/lite/user_config.theme.php | 2 +- 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/themes/lite/comment.theme.php b/themes/lite/comment.theme.php index 20717a981f..3be430a8d1 100644 --- a/themes/lite/comment.theme.php +++ b/themes/lite/comment.theme.php @@ -8,11 +8,13 @@ class CustomCommentListTheme extends CommentListTheme { protected function comment_to_html(Comment $comment, bool $trim = false): string { - return $this->rr(parent::comment_to_html($comment, $trim)); + $html = parent::comment_to_html($comment, $trim); + return "
    $html
    "; } protected function build_postbox(int $image_id): string { - return $this->rr(parent::build_postbox($image_id)); + $html = parent::build_postbox($image_id); + return "
    $html
    "; } } diff --git a/themes/lite/setup.theme.php b/themes/lite/setup.theme.php index 34f1854470..2629eacd43 100644 --- a/themes/lite/setup.theme.php +++ b/themes/lite/setup.theme.php @@ -41,6 +41,6 @@ protected function sb_to_html(SetupBlock $block): string "; - return $this->rr($html); + return "
    $html
    "; } } diff --git a/themes/lite/themelet.class.php b/themes/lite/themelet.class.php index 39c61b7209..923c50d68c 100644 --- a/themes/lite/themelet.class.php +++ b/themes/lite/themelet.class.php @@ -13,18 +13,6 @@ */ class Themelet extends BaseThemelet { - /** - * Put something in a rounded rectangle box; specific to the default theme. - */ - public function rr(string $html): string - { - return " -
    - $html -
    - "; - } - public function display_paginator(Page $page, string $base, ?string $query, int $page_number, int $total_pages, bool $show_random = false) { if ($total_pages == 0) { diff --git a/themes/lite/user_config.theme.php b/themes/lite/user_config.theme.php index b0c9f1fe82..2bd8c01d00 100644 --- a/themes/lite/user_config.theme.php +++ b/themes/lite/user_config.theme.php @@ -41,6 +41,6 @@ protected function sb_to_html(SetupBlock $block): string "; - return $this->rr($html); + return "
    $html
    "; } } From f8ec8447451f9a53064184335061389fd2d0d70d Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 15 Dec 2023 21:23:26 +0000 Subject: [PATCH 018/154] [themes] move disable_left to base class --- core/basepage.php | 6 ++++++ themes/danbooru/page.class.php | 7 ------- themes/danbooru2/page.class.php | 6 ------ themes/futaba/page.class.php | 6 ------ themes/lite/page.class.php | 7 ------- 5 files changed, 6 insertions(+), 26 deletions(-) diff --git a/core/basepage.php b/core/basepage.php index a691336a84..c016f375cc 100644 --- a/core/basepage.php +++ b/core/basepage.php @@ -113,6 +113,7 @@ public function set_redirect(string $redirect): void public string $title = ""; public string $heading = ""; public string $subheading = ""; + public bool $left_enabled = true; /** @var string[] */ public array $html_headers = []; @@ -157,6 +158,11 @@ public function flash(string $message): void $this->flash[] = $message; } + public function disable_left() + { + $this->left_enabled = false; + } + /** * Add a line to the HTML head section. */ diff --git a/themes/danbooru/page.class.php b/themes/danbooru/page.class.php index 3eee4ac05d..b14a417264 100644 --- a/themes/danbooru/page.class.php +++ b/themes/danbooru/page.class.php @@ -50,13 +50,6 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ class Page extends BasePage { - public bool $left_enabled = true; - - public function disable_left() - { - $this->left_enabled = false; - } - public function render() { global $config; diff --git a/themes/danbooru2/page.class.php b/themes/danbooru2/page.class.php index d4575d37fc..8bc283483e 100644 --- a/themes/danbooru2/page.class.php +++ b/themes/danbooru2/page.class.php @@ -51,12 +51,6 @@ class Page extends BasePage { - public bool $left_enabled = true; - public function disable_left() - { - $this->left_enabled = false; - } - public function render() { global $config; diff --git a/themes/futaba/page.class.php b/themes/futaba/page.class.php index 83be4b9132..4ff54ccbf5 100644 --- a/themes/futaba/page.class.php +++ b/themes/futaba/page.class.php @@ -6,12 +6,6 @@ class Page extends BasePage { - public bool $left_enabled = true; - public function disable_left() - { - $this->left_enabled = false; - } - public function render() { $left_block_html = ""; diff --git a/themes/lite/page.class.php b/themes/lite/page.class.php index d6d6063447..082a4b5f28 100644 --- a/themes/lite/page.class.php +++ b/themes/lite/page.class.php @@ -17,13 +17,6 @@ class Page extends BasePage { - public bool $left_enabled = true; - - public function disable_left() - { - $this->left_enabled = false; - } - public function render() { global $config; From 62e43545989d931bd7a5e617ebe298834b6e6341 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 16 Dec 2023 01:15:34 +0000 Subject: [PATCH 019/154] simplify --- themes/rule34v2/tag_edit.theme.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/themes/rule34v2/tag_edit.theme.php b/themes/rule34v2/tag_edit.theme.php index 5c9b1a3856..d1a5138e1b 100644 --- a/themes/rule34v2/tag_edit.theme.php +++ b/themes/rule34v2/tag_edit.theme.php @@ -25,10 +25,10 @@ public function display_mass_editor() $page->add_block(new Block("Mass Tag Edit", $html)); } - public function get_tag_editor_html(Image $image): \MicroHTML\HTMLElement + public function get_tag_editor_html(Image $image): HTMLElement { $h_tags = html_escape($image->get_tag_list()); - return \MicroHTML\rawHTML(" + return rawHTML(" Tags @@ -38,13 +38,13 @@ public function get_tag_editor_html(Image $image): \MicroHTML\HTMLElement "); } - public function get_source_editor_html(Image $image): \MicroHTML\HTMLElement + public function get_source_editor_html(Image $image): HTMLElement { global $user; $h_source = html_escape($image->get_source()); $f_source = $this->format_source($image->get_source()); $style = "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"; - return \MicroHTML\rawHTML(" + return rawHTML(" Source Link From dba8acea024d803dde8ece8fe649c6066d166b4c Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 16 Dec 2023 01:25:56 +0000 Subject: [PATCH 020/154] enable static analysis for all themes --- tests/phpstan.neon | 2 +- themes/rule34v2/index.theme.php | 4 +++- themes/rule34v2/page.class.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/phpstan.neon b/tests/phpstan.neon index 2a379a7f08..9faf61e304 100644 --- a/tests/phpstan.neon +++ b/tests/phpstan.neon @@ -4,6 +4,6 @@ parameters: - ../core - ../ext - ../tests - - ../themes/default + - ../themes ignoreErrors: - '#Access to an undefined property Shimmie2\\Image::\$#' diff --git a/themes/rule34v2/index.theme.php b/themes/rule34v2/index.theme.php index 94793bad09..f7ec7622f4 100644 --- a/themes/rule34v2/index.theme.php +++ b/themes/rule34v2/index.theme.php @@ -6,6 +6,8 @@ class CustomIndexTheme extends IndexTheme { + public static array $_search_query = []; + protected function build_table(array $images, ?string $query): string { global $user; @@ -27,7 +29,7 @@ public function display_page(Page $page, $images) $nav = $this->build_navigation($this->page_number, $this->total_pages, $this->search_terms); if (!empty($this->search_terms)) { - $page->_search_query = $this->search_terms; + static::$_search_query = $this->search_terms; } $page->add_block(new Block("Navigation", $nav, "left", 0)); diff --git a/themes/rule34v2/page.class.php b/themes/rule34v2/page.class.php index 94c84eca2e..6a1e9d8b3b 100644 --- a/themes/rule34v2/page.class.php +++ b/themes/rule34v2/page.class.php @@ -53,7 +53,7 @@ public function render() } } - $query = !empty($this->_search_query) ? html_escape(Tag::implode($this->_search_query)) : ""; + $query = !empty(CustomIndexTheme::$_search_query) ? html_escape(Tag::implode(CustomIndexTheme::$_search_query)) : ""; assert(!is_null($query)); # used in header.inc, do not remove :P $flash_html = $this->flash ? "".nl2br(html_escape(implode("\n", $this->flash)))."" : ""; $generated = autodate(date('c')); From b1c6894fbc581a04c8b5900b662c081595558769 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 16 Dec 2023 00:37:40 +0000 Subject: [PATCH 021/154] cleaner extension loading --- core/util.php | 26 +++++++++++--------------- index.php | 4 +--- tests/bootstrap.php | 9 +++------ 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/core/util.php b/core/util.php index 2e0e197e18..8a59321e77 100644 --- a/core/util.php +++ b/core/util.php @@ -565,11 +565,20 @@ function _load_core_files() )); } +function _load_extension_files() +{ + ExtensionInfo::load_all_extension_info(); + Extension::determine_enabled_extensions(); + require_all(zglob("ext/{".Extension::get_enabled_extensions_as_string()."}/main.php")); +} + function _load_theme_files() { $theme = get_theme(); - $files = _get_themelet_files($theme); - require_all($files); + require_once('themes/'.$theme.'/page.class.php'); + require_once('themes/'.$theme.'/themelet.class.php'); + require_all(zglob("ext/{".Extension::get_enabled_extensions_as_string()."}/theme.php")); + require_all(zglob('themes/'.$theme.'/{'.Extension::get_enabled_extensions_as_string().'}.theme.php')); } function _set_up_shimmie_environment(): void @@ -595,19 +604,6 @@ function _set_up_shimmie_environment(): void } -function _get_themelet_files(string $_theme): array -{ - $base_themelets = []; - $base_themelets[] = 'themes/'.$_theme.'/page.class.php'; - $base_themelets[] = 'themes/'.$_theme.'/themelet.class.php'; - - $ext_themelets = zglob("ext/{".Extension::get_enabled_extensions_as_string()."}/theme.php"); - $custom_themelets = zglob('themes/'.$_theme.'/{'.Extension::get_enabled_extensions_as_string().'}.theme.php'); - - return array_merge($base_themelets, $ext_themelets, $custom_themelets); -} - - /** * Used to display fatal errors to the web user. */ diff --git a/index.php b/index.php index 7aebad1c44..5b7cbb9604 100644 --- a/index.php +++ b/index.php @@ -50,9 +50,7 @@ $cache = loadCache(CACHE_DSN); $database = new Database(DATABASE_DSN); $config = new DatabaseConfig($database); -ExtensionInfo::load_all_extension_info(); -Extension::determine_enabled_extensions(); -require_all(zglob("ext/{".Extension::get_enabled_extensions_as_string()."}/main.php")); +_load_extension_files(); _load_theme_files(); $page = new Page(); _load_event_listeners(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 808539b4f4..cf1d7c4559 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -26,18 +26,15 @@ $_tracer->begin("bootstrap"); _load_core_files(); $cache = loadCache(CACHE_DSN); -$dsn = getenv("TEST_DSN"); -$database = new Database($dsn ? $dsn : "sqlite::memory:"); +$database = new Database(getenv("TEST_DSN") ?: "sqlite::memory:"); create_dirs(); create_tables($database); $config = new DatabaseConfig($database); -ExtensionInfo::load_all_extension_info(); -Extension::determine_enabled_extensions(); -require_all(zglob("ext/{".Extension::get_enabled_extensions_as_string()."}/main.php")); +_load_extension_files(); _load_theme_files(); $page = new Page(); _load_event_listeners(); -$config->set_string("thumb_engine", "static"); # GD has less overhead per-call +$config->set_string("thumb_engine", "static"); $config->set_bool("nice_urls", true); send_event(new DatabaseUpgradeEvent()); send_event(new InitExtEvent()); From 9a56b50d840b9a2a5b600a565d32c47f43db55e2 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 16 Dec 2023 10:42:32 +0000 Subject: [PATCH 022/154] [bulk_add_csv] align help --- ext/bulk_add_csv/main.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bulk_add_csv/main.php b/ext/bulk_add_csv/main.php index 868ae5ac5e..f04e93edc2 100644 --- a/ext/bulk_add_csv/main.php +++ b/ext/bulk_add_csv/main.php @@ -24,8 +24,8 @@ public function onPageRequest(PageRequestEvent $event) public function onCommand(CommandEvent $event) { if ($event->cmd == "help") { - print " bulk-add-csv [/path/to.csv]\n"; - print " Import this .csv file (refer to documentation)\n\n"; + print "\tbulk-add-csv [/path/to.csv]\n"; + print "\t\tImport this .csv file (refer to documentation)\n\n"; } if ($event->cmd == "bulk-add-csv") { global $user; From 71341be3a87570e141af249828c767046fca6f40 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 16 Dec 2023 10:52:50 +0000 Subject: [PATCH 023/154] move testcase to core, so that require(ext/X/*.php) can work --- core/testcase.php | 237 ++++++++++++++++++++++++++++++++++++++++++++ tests/bootstrap.php | 228 ------------------------------------------ 2 files changed, 237 insertions(+), 228 deletions(-) create mode 100644 core/testcase.php diff --git a/core/testcase.php b/core/testcase.php new file mode 100644 index 0000000000..db4b178703 --- /dev/null +++ b/core/testcase.php @@ -0,0 +1,237 @@ +begin(get_called_class()); + + self::create_user(self::$admin_name); + self::create_user(self::$user_name); + } + + public function setUp(): void + { + global $database, $_tracer; + $_tracer->begin($this->name()); + $_tracer->begin("setUp"); + $class = str_replace("Test", "", get_class($this)); + try { + if (!ExtensionInfo::get_for_extension_class($class)->is_supported()) { + $this->markTestSkipped("$class not supported with this database"); + } + } catch (ExtensionNotFound $e) { + // ignore - this is a core test rather than an extension test + } + + // Set up a clean environment for each test + self::log_out(); + foreach ($database->get_col("SELECT id FROM images") as $image_id) { + send_event(new ImageDeletionEvent(Image::by_id((int)$image_id), true)); + } + + $_tracer->end(); # setUp + $_tracer->begin("test"); + } + + public function tearDown(): void + { + global $_tracer; + $_tracer->end(); # test + $_tracer->end(); # $this->getName() + $_tracer->clear(); + $_tracer->flush("tests/trace.json"); + } + + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + global $_tracer; + $_tracer->end(); # get_called_class() + } + + protected static function create_user(string $name): void + { + if (is_null(User::by_name($name))) { + $userPage = new UserPage(); + $userPage->onUserCreation(new UserCreationEvent($name, $name, $name, "", false)); + assert(!is_null(User::by_name($name)), "Creation of user $name failed"); + } + } + + private static function check_args(?array $args): array + { + if (!$args) { + return []; + } + foreach ($args as $k => $v) { + if (is_array($v)) { + $args[$k] = $v; + } else { + $args[$k] = (string)$v; + } + } + return $args; + } + + protected static function request($page_name, $get_args = null, $post_args = null): Page + { + // use a fresh page + global $page; + $get_args = self::check_args($get_args); + $post_args = self::check_args($post_args); + + if (str_contains($page_name, "?")) { + throw new \RuntimeException("Query string included in page name"); + } + $_SERVER['REQUEST_URI'] = make_link($page_name, http_build_query($get_args)); + $_GET = $get_args; + $_POST = $post_args; + $page = new Page(); + send_event(new PageRequestEvent($page_name)); + if ($page->mode == PageMode::REDIRECT) { + $page->code = 302; + } + return $page; + } + + protected static function get_page($page_name, $args = null): Page + { + return self::request($page_name, $args, null); + } + + protected static function post_page($page_name, $args = null): Page + { + return self::request($page_name, null, $args); + } + + // page things + protected function assert_title(string $title): void + { + global $page; + $this->assertStringContainsString($title, $page->title); + } + + protected function assert_title_matches($title): void + { + global $page; + $this->assertStringMatchesFormat($title, $page->title); + } + + protected function assert_no_title(string $title): void + { + global $page; + $this->assertStringNotContainsString($title, $page->title); + } + + protected function assert_response(int $code): void + { + global $page; + $this->assertEquals($code, $page->code); + } + + protected function page_to_text(string $section = null): string + { + global $page; + if ($page->mode == PageMode::PAGE) { + $text = $page->title . "\n"; + foreach ($page->blocks as $block) { + if (is_null($section) || $section == $block->section) { + $text .= $block->header . "\n"; + $text .= $block->body . "\n\n"; + } + } + return $text; + } elseif ($page->mode == PageMode::DATA) { + return $page->data; + } else { + $this->fail("Page mode is not PAGE or DATA"); + } + } + + protected function assert_text(string $text, string $section = null): void + { + $this->assertStringContainsString($text, $this->page_to_text($section)); + } + + protected function assert_no_text(string $text, string $section = null): void + { + $this->assertStringNotContainsString($text, $this->page_to_text($section)); + } + + protected function assert_content(string $content): void + { + global $page; + $this->assertStringContainsString($content, $page->data); + } + + protected function assert_no_content(string $content): void + { + global $page; + $this->assertStringNotContainsString($content, $page->data); + } + + protected function assert_search_results($tags, $results): void + { + $images = Search::find_images(0, null, $tags); + $ids = []; + foreach ($images as $image) { + $ids[] = $image->id; + } + $this->assertEquals($results, $ids); + } + + // user things + protected static function log_in_as_admin(): void + { + send_event(new UserLoginEvent(User::by_name(self::$admin_name))); + } + + protected static function log_in_as_user(): void + { + send_event(new UserLoginEvent(User::by_name(self::$user_name))); + } + + protected static function log_out(): void + { + global $config; + send_event(new UserLoginEvent(User::by_id($config->get_int("anon_id", 0)))); + } + + // post things + protected function post_image(string $filename, string $tags): int + { + $dae = send_event(new DataUploadEvent($filename, [ + "filename" => $filename, + "tags" => Tag::explode($tags), + "source" => null, + ])); + // if($dae->image_id == -1) throw new \Exception("Upload failed :("); + return $dae->image_id; + } + + protected function delete_image(int $image_id): void + { + $img = Image::by_id($image_id); + if ($img) { + send_event(new ImageDeletionEvent($img, true)); + } + } + } +} else { + abstract class ShimmiePHPUnitTestCase + { + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index cf1d7c4559..c4344ad0ec 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -4,8 +4,6 @@ namespace Shimmie2; -use PHPUnit\Framework\TestCase; - chdir(dirname(dirname(__FILE__))); require_once "core/sanitize_php.php"; require_once "vendor/autoload.php"; @@ -40,229 +38,3 @@ send_event(new InitExtEvent()); $user = User::by_id($config->get_int("anon_id", 0)); $_tracer->end(); - -abstract class ShimmiePHPUnitTestCase extends TestCase -{ - protected static string $anon_name = "anonymous"; - protected static string $admin_name = "demo"; - protected static string $user_name = "test"; - protected string $wipe_time = "test"; - - public static function setUpBeforeClass(): void - { - parent::setUpBeforeClass(); - global $_tracer; - $_tracer->begin(get_called_class()); - - self::create_user(self::$admin_name); - self::create_user(self::$user_name); - } - - public function setUp(): void - { - global $database, $_tracer; - $_tracer->begin($this->name()); - $_tracer->begin("setUp"); - $class = str_replace("Test", "", get_class($this)); - try { - if (!ExtensionInfo::get_for_extension_class($class)->is_supported()) { - $this->markTestSkipped("$class not supported with this database"); - } - } catch (ExtensionNotFound $e) { - // ignore - this is a core test rather than an extension test - } - - // Set up a clean environment for each test - self::log_out(); - foreach ($database->get_col("SELECT id FROM images") as $image_id) { - send_event(new ImageDeletionEvent(Image::by_id((int)$image_id), true)); - } - - $_tracer->end(); # setUp - $_tracer->begin("test"); - } - - public function tearDown(): void - { - global $_tracer; - $_tracer->end(); # test - $_tracer->end(); # $this->getName() - $_tracer->clear(); - $_tracer->flush("tests/trace.json"); - } - - public static function tearDownAfterClass(): void - { - parent::tearDownAfterClass(); - global $_tracer; - $_tracer->end(); # get_called_class() - } - - protected static function create_user(string $name): void - { - if (is_null(User::by_name($name))) { - $userPage = new UserPage(); - $userPage->onUserCreation(new UserCreationEvent($name, $name, $name, "", false)); - assert(!is_null(User::by_name($name)), "Creation of user $name failed"); - } - } - - private static function check_args(?array $args): array - { - if (!$args) { - return []; - } - foreach ($args as $k => $v) { - if (is_array($v)) { - $args[$k] = $v; - } else { - $args[$k] = (string)$v; - } - } - return $args; - } - - protected static function request($page_name, $get_args = null, $post_args = null): Page - { - // use a fresh page - global $page; - $get_args = self::check_args($get_args); - $post_args = self::check_args($post_args); - - if (str_contains($page_name, "?")) { - throw new \RuntimeException("Query string included in page name"); - } - $_SERVER['REQUEST_URI'] = make_link($page_name, http_build_query($get_args)); - $_GET = $get_args; - $_POST = $post_args; - $page = new Page(); - send_event(new PageRequestEvent($page_name)); - if ($page->mode == PageMode::REDIRECT) { - $page->code = 302; - } - return $page; - } - - protected static function get_page($page_name, $args = null): Page - { - return self::request($page_name, $args, null); - } - - protected static function post_page($page_name, $args = null): Page - { - return self::request($page_name, null, $args); - } - - // page things - protected function assert_title(string $title): void - { - global $page; - $this->assertStringContainsString($title, $page->title); - } - - protected function assert_title_matches($title): void - { - global $page; - $this->assertStringMatchesFormat($title, $page->title); - } - - protected function assert_no_title(string $title): void - { - global $page; - $this->assertStringNotContainsString($title, $page->title); - } - - protected function assert_response(int $code): void - { - global $page; - $this->assertEquals($code, $page->code); - } - - protected function page_to_text(string $section = null): string - { - global $page; - if ($page->mode == PageMode::PAGE) { - $text = $page->title . "\n"; - foreach ($page->blocks as $block) { - if (is_null($section) || $section == $block->section) { - $text .= $block->header . "\n"; - $text .= $block->body . "\n\n"; - } - } - return $text; - } elseif ($page->mode == PageMode::DATA) { - return $page->data; - } else { - $this->fail("Page mode is not PAGE or DATA"); - } - } - - protected function assert_text(string $text, string $section = null): void - { - $this->assertStringContainsString($text, $this->page_to_text($section)); - } - - protected function assert_no_text(string $text, string $section = null): void - { - $this->assertStringNotContainsString($text, $this->page_to_text($section)); - } - - protected function assert_content(string $content): void - { - global $page; - $this->assertStringContainsString($content, $page->data); - } - - protected function assert_no_content(string $content): void - { - global $page; - $this->assertStringNotContainsString($content, $page->data); - } - - protected function assert_search_results($tags, $results): void - { - $images = Search::find_images(0, null, $tags); - $ids = []; - foreach ($images as $image) { - $ids[] = $image->id; - } - $this->assertEquals($results, $ids); - } - - // user things - protected static function log_in_as_admin(): void - { - send_event(new UserLoginEvent(User::by_name(self::$admin_name))); - } - - protected static function log_in_as_user(): void - { - send_event(new UserLoginEvent(User::by_name(self::$user_name))); - } - - protected static function log_out(): void - { - global $config; - send_event(new UserLoginEvent(User::by_id($config->get_int("anon_id", 0)))); - } - - // post things - protected function post_image(string $filename, string $tags): int - { - $dae = send_event(new DataUploadEvent($filename, [ - "filename" => $filename, - "tags" => Tag::explode($tags), - "source" => null, - ])); - // if($dae->image_id == -1) throw new \Exception("Upload failed :("); - return $dae->image_id; - } - - protected function delete_image(int $image_id): void - { - $img = Image::by_id($image_id); - if ($img) { - send_event(new ImageDeletionEvent($img, true)); - } - } -} From f00a4e3d908408662e4546c18b24790edfa93dda Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 16 Dec 2023 11:04:52 +0000 Subject: [PATCH 024/154] fewer loose functions --- core/basepage.php | 10 +++------- core/user.php | 6 ------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/core/basepage.php b/core/basepage.php index c016f375cc..dbf730fd65 100644 --- a/core/basepage.php +++ b/core/basepage.php @@ -511,8 +511,9 @@ protected function get_nav_links(): array } $sub_links = $sub_links ?? []; - usort($nav_links, "Shimmie2\sort_nav_links"); - usort($sub_links, "Shimmie2\sort_nav_links"); + + usort($nav_links, fn (NavLink $a, NavLink $b) => $a->order - $b->order); + usort($sub_links, fn (NavLink $a, NavLink $b) => $a->order - $b->order); return [$nav_links, $sub_links]; } @@ -705,8 +706,3 @@ public static function is_active(array $pages_matched, string $url = null): bool return false; } } - -function sort_nav_links(NavLink $a, NavLink $b): int -{ - return $a->order - $b->order; -} diff --git a/core/user.php b/core/user.php index 521e35f269..8dc8c1309b 100644 --- a/core/user.php +++ b/core/user.php @@ -11,12 +11,6 @@ use function MicroHTML\INPUT; -function _new_user(array $row): User -{ - return new User($row); -} - - /** * Class User * From 282d13f63243ce9fe0ebf927b8d3e6ace25737c7 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 7 Nov 2023 20:58:46 +0000 Subject: [PATCH 025/154] Use nginx Unit rather than the php development server --- .devcontainer/devcontainer.json | 10 ++- .docker/entrypoint.d/config.json | 76 ++++++++++++++++++++++ .docker/entrypoint.sh | 106 +++++++++++++++++++++++++++++++ Dockerfile | 26 ++++---- core/util.php | 14 +++- tests/docker-init.sh | 12 ---- 6 files changed, 216 insertions(+), 28 deletions(-) create mode 100644 .docker/entrypoint.d/config.json create mode 100755 .docker/entrypoint.sh delete mode 100644 tests/docker-init.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6acb534bf9..43bbf0a1b2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,6 +8,9 @@ "target": "devcontainer" }, + "workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind", + "workspaceFolder": "/app", + // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, @@ -15,8 +18,11 @@ "forwardPorts": [8000], // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "cd /workspaces/shimmie2 && /usr/bin/php -S 0.0.0.0:8000 tests/router.php 2>&1 | grep --line-buffered -vE \" (Accepted|Closing)\"", - + "postCreateCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", + "containerEnv": { + "UID": "2000", + "GID": "2000" + }, "customizations": { "vscode": { "extensions": [ diff --git a/.docker/entrypoint.d/config.json b/.docker/entrypoint.d/config.json new file mode 100644 index 0000000000..3d7a9ff2da --- /dev/null +++ b/.docker/entrypoint.d/config.json @@ -0,0 +1,76 @@ +{ + "listeners": { + "*:8000": { + "pass": "routes", + "forwarded": { + "client_ip": "X-Forwarded-For", + "recursive": false, + "source": [ + "172.17.0.0/16" + ] + } + } + }, + "routes": [ + { + "match": { + "uri": "~/_(thumbs|images)/.*" + }, + "action": { + "share": [ + "`/app/data/${uri.replace(/_(thumbs|images)\\/(..)(..)(.*?)\\/.*/, '$1/$2/$3/$2$3$4')}`", + "`/app/data/${uri.replace(/_(thumbs|images)\\/(..)(.*?)\\/.*/, '$1/$2/$2$3')}`" + ], + "response_headers": { + "Cache-Control": "public, max-age=31556926" + } + } + }, + { + "action": { + "share": [ + "/app/ext/static_files/static/$uri", + "/app/$uri" + ], + "types": [ + "image/*", + "application/javascript", + "text/css", + "!" + ], + "response_headers": { + "Cache-Control": "public, max-age=31556926" + }, + "fallback": { + "pass": "applications/shimmie" + } + } + } + ], + "applications": { + "shimmie": { + "type": "php", + "user": "shimmie", + "root": "/app/", + "script": "index.php", + "working_directory": "/app/", + "options": { + "admin": { + "memory_limit": "256M", + "upload_max_filesize": "50M", + "post_max_size": "50M" + } + }, + "processes": { + "max": 8, + "spare": 2, + "idle_timeout": 60 + } + } + }, + "settings": { + "http": { + "max_body_size": 104857600 + } + } +} diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh new file mode 100755 index 0000000000..9649d66c32 --- /dev/null +++ b/.docker/entrypoint.sh @@ -0,0 +1,106 @@ +#!/bin/sh + +set -e + +# if user shimmie doesn't already exist, create it +if ! id -u shimmie >/dev/null 2>&1; then + groupadd -g $GID shimmie || true + useradd -ms /bin/bash -u $UID -g $GID shimmie || true +fi +mkdir -p /app/data +chown shimmie:shimmie /app/data + +rm -rf /var/lib/unit/* + +WAITLOOPS=5 +SLEEPSEC=1 + +curl_put() +{ + RET=$(/usr/bin/curl -s -w '%{http_code}' -X PUT --data-binary @$1 --unix-socket /var/run/control.unit.sock http://localhost/$2) + RET_BODY=$(echo $RET | /bin/sed '$ s/...$//') + RET_STATUS=$(echo $RET | /usr/bin/tail -c 4) + if [ "$RET_STATUS" -ne "200" ]; then + echo "$0: Error: HTTP response status code is '$RET_STATUS'" + echo "$RET_BODY" + return 1 + else + echo "$0: OK: HTTP response status code is '$RET_STATUS'" + echo "$RET_BODY" + fi + return 0 +} + +if [ "$1" = "unitd" ] || [ "$1" = "unitd-debug" ]; then + if /usr/bin/find "/var/lib/unit/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then + echo "$0: /var/lib/unit/ is not empty, skipping initial configuration..." + else + echo "$0: Launching Unit daemon to perform initial configuration..." + /usr/sbin/$1 --control unix:/var/run/control.unit.sock + + for i in $(/usr/bin/seq $WAITLOOPS); do + if [ ! -S /var/run/control.unit.sock ]; then + echo "$0: Waiting for control socket to be created..." + /bin/sleep $SLEEPSEC + else + break + fi + done + # even when the control socket exists, it does not mean unit has finished initialisation + # this curl call will get a reply once unit is fully launched + /usr/bin/curl -s -X GET --unix-socket /var/run/control.unit.sock http://localhost/ + if /usr/bin/find "/app/.docker/entrypoint.d/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then + echo "$0: /app/.docker/entrypoint.d/ is not empty, applying initial configuration..." + + echo "$0: Looking for certificate bundles in /app/.docker/entrypoint.d/..." + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -name "*.pem"); do + echo "$0: Uploading certificates bundle: $f" + curl_put $f "certificates/$(basename $f .pem)" + done + echo "$0: Looking for JavaScript modules in /app/.docker/entrypoint.d/..." + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -name "*.js"); do + echo "$0: Uploading JavaScript module: $f" + curl_put $f "js_modules/$(basename $f .js)" + done + + echo "$0: Looking for configuration snippets in /app/.docker/entrypoint.d/..." + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -name "*.json"); do + echo "$0: Applying configuration $f"; + curl_put $f "config" + done + echo "$0: Looking for shell scripts in /app/.docker/entrypoint.d/..." + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -name "*.sh"); do + echo "$0: Launching $f"; + "$f" + done + + # warn on filetypes we don't know what to do with + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -not -name "*.sh" -not -name "*.json" -not -name "*.pem" -not -name "*.js"); do + echo "$0: Ignoring $f"; + done + else + echo "$0: /app/.docker/entrypoint.d/ is empty, creating 'welcome' configuration..." + curl_put /usr/share/unit/welcome/welcome.json "config" + fi + echo "$0: Stopping Unit daemon after initial configuration..." + kill -TERM $(/bin/cat /var/run/unit.pid) + + for i in $(/usr/bin/seq $WAITLOOPS); do + if [ -S /var/run/control.unit.sock ]; then + echo "$0: Waiting for control socket to be removed..." + /bin/sleep $SLEEPSEC + else + break + fi + done + if [ -S /var/run/control.unit.sock ]; then + kill -KILL $(/bin/cat /var/run/unit.pid) + rm -f /var/run/control.unit.sock + fi + echo + echo "$0: Unit initial configuration complete; ready for start up..." + echo + fi +fi + +exec "$@" diff --git a/Dockerfile b/Dockerfile index 7477642cc3..75f1898813 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,15 @@ ARG PHP_VERSION=8.2 # Install base packages which all stages (build, test, run) need FROM debian:bookworm AS base -RUN apt update && apt upgrade -y && apt install -y \ +RUN apt update && apt upgrade -y +RUN apt update && apt install -y curl +RUN curl --output /usr/share/keyrings/nginx-keyring.gpg https://unit.nginx.org/keys/nginx-keyring.gpg +RUN echo 'deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bookworm unit' > /etc/apt/sources.list.d/unit.list +RUN apt update && apt install -y \ php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \ php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 \ - gosu curl imagemagick ffmpeg zip unzip && \ - rm -rf /var/lib/apt/lists/* + gosu curl imagemagick ffmpeg zip unzip git unit unit-php +RUN apt update && apt install -y procps net-tools # Composer has 100MB of dependencies, and we only need that during build and test FROM base AS composer @@ -15,7 +19,7 @@ ENV XDEBUG_MODE=coverage # "Build" shimmie (composer install - done in its own stage so that we don't # need to include all the composer fluff in the final image) -FROM composer AS app +FROM composer AS build COPY composer.json composer.lock /app/ WORKDIR /app RUN composer install --no-dev @@ -39,16 +43,12 @@ RUN [ $RUN_TESTS = false ] || (\ # Devcontainer target FROM composer AS devcontainer -RUN apt update && apt upgrade -y && apt install -y git && rm -rf /var/lib/apt/lists/* +EXPOSE 8000 # Actually run shimmie -FROM base +FROM base AS run EXPOSE 8000 HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 -ENV UID=1000 \ - GID=1000 \ - UPLOAD_MAX_FILESIZE=50M -COPY --from=app /app /app - -WORKDIR /app -CMD ["/bin/sh", "/app/tests/docker-init.sh"] +COPY --from=build /app /app +ENTRYPOINT ["/app/.docker/unit-entrypoint.sh"] +CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"] \ No newline at end of file diff --git a/core/util.php b/core/util.php index 8a59321e77..d2150d276a 100644 --- a/core/util.php +++ b/core/util.php @@ -686,7 +686,19 @@ function _get_user(): User function _get_query(): string { - return (@$_POST["q"] ?: @$_GET["q"]) ?: "/"; + // if query is explicitly set, use it + $q = @$_POST["q"] ?: @$_GET["q"]; + if(!empty($q)) { + return $q; + } + // if we're just looking at index.php, use the default query + elseif (str_contains($_SERVER['REQUEST_URI'], "index.php")) { + return "/"; + } + // otherwise, use the request URI + else { + return explode("?", $_SERVER['REQUEST_URI'])[0]; + } } diff --git a/tests/docker-init.sh b/tests/docker-init.sh deleted file mode 100644 index e83854435b..0000000000 --- a/tests/docker-init.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -groupadd -g $GID shimmie || true -useradd -ms /bin/bash -u $UID -g $GID shimmie -mkdir -p /app/data -chown $UID:$GID /app/data -export PHP_CLI_SERVER_WORKERS=8 -exec gosu shimmie:shimmie \ - /usr/bin/php \ - -d upload_max_filesize=$UPLOAD_MAX_FILESIZE \ - -d post_max_size=$UPLOAD_MAX_FILESIZE \ - -S 0.0.0.0:8000 \ - tests/router.php 2>&1 | grep --line-buffered -vE " (Accepted|Closing)" From 77892c5d928e607064ac7bc1f86f5c0d7bce3715 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 16 Dec 2023 23:44:46 +0000 Subject: [PATCH 026/154] fix entrypoint --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 75f1898813..b37994cba6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,5 +50,5 @@ FROM base AS run EXPOSE 8000 HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 COPY --from=build /app /app -ENTRYPOINT ["/app/.docker/unit-entrypoint.sh"] +ENTRYPOINT ["/app/.docker/entrypoint.sh"] CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"] \ No newline at end of file From 64e6460f222f812c95f0026f31d68a4a7102c175 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 17 Dec 2023 00:08:06 +0000 Subject: [PATCH 027/154] don't look at cookies if bearer token was ok --- core/util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util.php b/core/util.php index d2150d276a..9c695e7d3a 100644 --- a/core/util.php +++ b/core/util.php @@ -673,7 +673,7 @@ function _get_user(): User } } } - if ($page->get_cookie("user") && $page->get_cookie("session")) { + if (is_null($my_user) && $page->get_cookie("user") && $page->get_cookie("session")) { $my_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session")); } if (is_null($my_user)) { From bfce3fa82ff02732310de54c5b76a174285aa00b Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 17 Dec 2023 22:43:06 +0000 Subject: [PATCH 028/154] build future-proofing --- .github/workflows/tests.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f6104ff8be..6f714292f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@master with: - php-version: 8.1 + php-version: 8.3 - name: Format run: ./vendor/bin/php-cs-fixer fix && git diff --exit-code @@ -122,9 +122,13 @@ jobs: if [[ "${{ matrix.database }}" == "sqlite" ]]; then export TEST_DSN="sqlite:data/shimmie.sqlite" fi - vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover + if [[ "${{ matrix.php }}" == "8.3" ]]; then + vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover + else + vendor/bin/phpunit --configuration tests/phpunit.xml + fi - name: Upload coverage - if: matrix.php == '8.1' + if: matrix.php == '8.3' run: | vendor/bin/ocular code-coverage:upload --format=php-clover data/coverage.clover From 7fae66e7e8824cd69a2a399985e03f3b3c045baf Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 18 Dec 2023 00:43:34 +0000 Subject: [PATCH 029/154] Set default UID/GID --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b37994cba6..fdbc94822a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,6 +49,7 @@ EXPOSE 8000 FROM base AS run EXPOSE 8000 HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 +ENV UID=1000 GID=1000 COPY --from=build /app /app ENTRYPOINT ["/app/.docker/entrypoint.sh"] -CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"] \ No newline at end of file +CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"] From 6069e9455a18c6f367737185e1e3a746f1f2958d Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 18 Dec 2023 01:23:46 +0000 Subject: [PATCH 030/154] UPLOAD_MAX_FILESIZE with unit --- .docker/entrypoint.d/{config.json => config.json.tmpl} | 6 +++--- .docker/entrypoint.sh | 2 ++ .gitignore | 1 + Dockerfile | 5 ++--- 4 files changed, 8 insertions(+), 6 deletions(-) rename .docker/entrypoint.d/{config.json => config.json.tmpl} (92%) diff --git a/.docker/entrypoint.d/config.json b/.docker/entrypoint.d/config.json.tmpl similarity index 92% rename from .docker/entrypoint.d/config.json rename to .docker/entrypoint.d/config.json.tmpl index 3d7a9ff2da..c306a59134 100644 --- a/.docker/entrypoint.d/config.json +++ b/.docker/entrypoint.d/config.json.tmpl @@ -57,8 +57,8 @@ "options": { "admin": { "memory_limit": "256M", - "upload_max_filesize": "50M", - "post_max_size": "50M" + "upload_max_filesize": "$UPLOAD_MAX_FILESIZE", + "post_max_size": "$UPLOAD_MAX_FILESIZE" } }, "processes": { @@ -70,7 +70,7 @@ }, "settings": { "http": { - "max_body_size": 104857600 + "max_body_size": 1048576000 } } } diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh index 9649d66c32..16ada38635 100755 --- a/.docker/entrypoint.sh +++ b/.docker/entrypoint.sh @@ -12,6 +12,8 @@ chown shimmie:shimmie /app/data rm -rf /var/lib/unit/* +envsubst '$UPLOAD_MAX_FILESIZE' < /app/.docker/entrypoint.d/config.json.tmpl > /app/.docker/entrypoint.d/config.json + WAITLOOPS=5 SLEEPSEC=1 diff --git a/.gitignore b/.gitignore index 2edf41700a..607cbefcd5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ thumbs *.sqlite *.cache trace.json +.docker/entrypoint.d/config.json #Composer composer.phar diff --git a/Dockerfile b/Dockerfile index fdbc94822a..e7a0b74dd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,8 +9,7 @@ RUN echo 'deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages RUN apt update && apt install -y \ php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \ php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 \ - gosu curl imagemagick ffmpeg zip unzip git unit unit-php -RUN apt update && apt install -y procps net-tools + gosu curl imagemagick ffmpeg zip unzip git unit unit-php gettext procps net-tools # Composer has 100MB of dependencies, and we only need that during build and test FROM base AS composer @@ -49,7 +48,7 @@ EXPOSE 8000 FROM base AS run EXPOSE 8000 HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 -ENV UID=1000 GID=1000 +ENV UID=1000 GID=1000 UPLOAD_MAX_FILESIZE=50M COPY --from=build /app /app ENTRYPOINT ["/app/.docker/entrypoint.sh"] CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"] From 095a51a4d204312fb41ed4e982a11fd555945760 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 18 Dec 2023 01:26:31 +0000 Subject: [PATCH 031/154] [setup] column-width is standard now --- ext/setup/style.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ext/setup/style.css b/ext/setup/style.css index bc460fa4e6..c12e323b35 100644 --- a/ext/setup/style.css +++ b/ext/setup/style.css @@ -1,7 +1,5 @@ .setupblocks { column-width: 400px; - -moz-column-width: 400px; - -webkit-column-width: 400px; max-width: 1200px; margin: auto; } @@ -9,11 +7,7 @@ .setupblock { break-inside: avoid; - -moz-break-inside: avoid; - -webkit-break-inside: avoid; column-break-inside: avoid; - -moz-column-break-inside: avoid; - -webkit-column-break-inside: avoid; text-align: center; width: 90%; } From 0df98d6ce10f544ef8753252a37cbe4c302205c3 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 18 Dec 2023 01:46:55 +0000 Subject: [PATCH 032/154] update readme badges --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d37e9e519..cf05244f4b 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,10 @@ # Shimmie -[![Test & Publish](https://github.com/shish/shimmie2/workflows/Test%20&%20Publish/badge.svg)](https://github.com/shish/shimmie2/actions) +[![Tests](https://github.com/shish/shimmie2/workflows/Tests/badge.svg?branch=main)](https://github.com/shish/shimmie2/actions) [![Code Quality](https://scrutinizer-ci.com/g/shish/shimmie2/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/shish/shimmie2/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=master) +[![Matrix](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#shimmie:matrix.org) # Documentation From 60da9efba92815e0c9cc359c8f273bd80617cd08 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 18 Dec 2023 10:18:20 +0000 Subject: [PATCH 033/154] github is standard enough now --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index cf05244f4b..5be01518a5 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,6 @@ * [High-performance notes](https://github.com/shish/shimmie2/wiki/Performance) -# Contact - -Email: webmaster at shishnet.org - -Issue/Bug tracker: https://github.com/shish/shimmie2/issues - - # Licence All code is released under the [GNU GPL Version 2](https://www.gnu.org/licenses/gpl-2.0.html) unless mentioned otherwise. From 9e7f3cb3970072c552518117f3f140348e35d1d8 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 19 Dec 2023 11:36:35 +0000 Subject: [PATCH 034/154] Make SHM_POST_INFO more logical Rather than having a boolean for view or edit + optional editor, use the existence of the editor to know if a field is editable --- core/microhtml.php | 28 ++++++++------- ext/artists/theme.php | 1 - ext/image_view_counter/main.php | 2 +- ext/post_titles/theme.php | 3 +- ext/rating/theme.php | 3 +- ext/relationships/theme.php | 3 +- ext/rule34/main.php | 1 - ext/tag_edit/theme.php | 14 +++----- ext/view/main.php | 2 +- themes/rule34v2/tag_edit.theme.php | 56 +++++++++++++----------------- 10 files changed, 50 insertions(+), 63 deletions(-) diff --git a/core/microhtml.php b/core/microhtml.php index aadd633df0..e7f4b71e41 100644 --- a/core/microhtml.php +++ b/core/microhtml.php @@ -15,6 +15,7 @@ use function MicroHTML\PRE; use function MicroHTML\P; use function MicroHTML\SELECT; +use function MicroHTML\SPAN; use function MicroHTML\TABLE; use function MicroHTML\THEAD; use function MicroHTML\TFOOT; @@ -154,17 +155,20 @@ function SHM_OPTION(string $value, string $text, bool $selected = false): HTMLEl function SHM_POST_INFO( HTMLElement|string $title, - bool $can_edit, - HTMLElement|string $view, - HTMLElement|string $edit = "", + HTMLElement|string|null $view = null, + HTMLElement|string|null $edit = null, ): HTMLElement { - return TR( - TH(["width" => "50px"], $title), - $can_edit ? - emptyHTML( - TD(["class" => "view"], $view), - TD(["class" => "edit"], $edit), - ) : - TD($view) - ); + if(!is_null($view) && !is_null($edit)) { + $show = emptyHTML( + SPAN(["class" => "view"], $view), + SPAN(["class" => "edit"], $edit), + ); + } elseif(!is_null($edit)) { + $show = $edit; + } elseif(!is_null($view)) { + $show = $view; + } else { + $show = "???"; + } + return TR(TH(["width" => "50px"], $title), TD($show)); } diff --git a/ext/artists/theme.php b/ext/artists/theme.php index 5db8f1e611..12e9e9219c 100644 --- a/ext/artists/theme.php +++ b/ext/artists/theme.php @@ -15,7 +15,6 @@ public function get_author_editor_html(string $author): HTMLElement { return SHM_POST_INFO( "Author", - true, $author, INPUT(["type" => "text", "name" => "tag_edit__author", "value" => $author]) ); diff --git a/ext/image_view_counter/main.php b/ext/image_view_counter/main.php index 269bd09508..cb4b945a49 100644 --- a/ext/image_view_counter/main.php +++ b/ext/image_view_counter/main.php @@ -70,7 +70,7 @@ public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event) ["image_id" => $event->image->id] ); - $event->add_part(SHM_POST_INFO("Views", false, $view_count, ""), 38); + $event->add_part(SHM_POST_INFO("Views", $view_count), 38); } } diff --git a/ext/post_titles/theme.php b/ext/post_titles/theme.php index 72ead5c88e..f468fea235 100644 --- a/ext/post_titles/theme.php +++ b/ext/post_titles/theme.php @@ -14,9 +14,8 @@ public function get_title_set_html(string $title, bool $can_set): HTMLElement { return SHM_POST_INFO( "Title", - $can_set, $title, - INPUT(["type" => "text", "name" => "post_title", "value" => $title]) + $can_set ? INPUT(["type" => "text", "name" => "post_title", "value" => $title]) : null ); } } diff --git a/ext/rating/theme.php b/ext/rating/theme.php index 654e9482bd..a3bb0cd0c3 100644 --- a/ext/rating/theme.php +++ b/ext/rating/theme.php @@ -20,9 +20,8 @@ public function get_rater_html(int $image_id, string $rating, bool $can_rate): H { return SHM_POST_INFO( "Rating", - $can_rate, A(["href" => search_link(["rating=$rating"])], Ratings::rating_to_human($rating)), - $this->get_selection_rater_html("rating", selected_options: [$rating]) + $can_rate ? $this->get_selection_rater_html("rating", selected_options: [$rating]) : null ); } diff --git a/ext/relationships/theme.php b/ext/relationships/theme.php index 0a8af4a560..9b976a32c9 100644 --- a/ext/relationships/theme.php +++ b/ext/relationships/theme.php @@ -39,9 +39,8 @@ public function get_parent_editor_html(Image $image): HTMLElement return SHM_POST_INFO( "Parent", - !$user->is_anonymous(), strval($image->parent_id) ?: "None", - INPUT(["type" => "number", "name" => "tag_edit__parent", "value" => $image->parent_id]) + !$user->is_anonymous() ? INPUT(["type" => "number", "name" => "tag_edit__parent", "value" => $image->parent_id]) : null ); } diff --git a/ext/rule34/main.php b/ext/rule34/main.php index ee3da462b7..7eebd6de03 100644 --- a/ext/rule34/main.php +++ b/ext/rule34/main.php @@ -41,7 +41,6 @@ public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event) $event->add_part( SHM_POST_INFO( "Links", - false, emptyHTML( A(["href" => $url0], "File Only"), " (", diff --git a/ext/tag_edit/theme.php b/ext/tag_edit/theme.php index 66f4c8a241..b0e9a3d8cb 100644 --- a/ext/tag_edit/theme.php +++ b/ext/tag_edit/theme.php @@ -56,16 +56,15 @@ public function get_tag_editor_html(Image $image): HTMLElement return SHM_POST_INFO( "Tags", - $user->can(Permissions::EDIT_IMAGE_TAG), joinHTML(", ", $tag_links), - INPUT([ + $user->can(Permissions::EDIT_IMAGE_TAG) ? INPUT([ "class" => "autocomplete_tags", "type" => "text", "name" => "tag_edit__tags", "value" => $image->get_tag_list(), "id" => "tag_editor", "autocomplete" => "off" - ]) + ]) : null ); } @@ -77,9 +76,8 @@ public function get_user_editor_html(Image $image): HTMLElement $ip = $user->can(Permissions::VIEW_IP) ? rawHTML(" (" . show_ip($image->owner_ip, "Post posted {$image->posted}") . ")") : ""; $info = SHM_POST_INFO( "Uploader", - $user->can(Permissions::EDIT_IMAGE_OWNER), emptyHTML(A(["class" => "username", "href" => make_link("user/$owner")], $owner), $ip, ", ", $date), - INPUT(["type" => "text", "name" => "tag_edit__owner", "value" => $owner]) + $user->can(Permissions::EDIT_IMAGE_OWNER) ? INPUT(["type" => "text", "name" => "tag_edit__owner", "value" => $owner]) : null ); // SHM_POST_INFO returns a TR, let's sneakily append // a TD with the avatar in it @@ -97,12 +95,11 @@ public function get_source_editor_html(Image $image): HTMLElement global $user; return SHM_POST_INFO( "Source", - $user->can(Permissions::EDIT_IMAGE_SOURCE), DIV( ["style" => "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"], $this->format_source($image->get_source()) ), - INPUT(["type" => "text", "name" => "tag_edit__source", "value" => $image->get_source()]) + $user->can(Permissions::EDIT_IMAGE_SOURCE) ? INPUT(["type" => "text", "name" => "tag_edit__source", "value" => $image->get_source()]) : null ); } @@ -127,9 +124,8 @@ public function get_lock_editor_html(Image $image): HTMLElement global $user; return SHM_POST_INFO( "Locked", - $user->can(Permissions::EDIT_IMAGE_LOCK), $image->is_locked() ? "Yes (Only admins may edit these details)" : "No", - INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()]) + $user->can(Permissions::EDIT_IMAGE_LOCK) ? INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()]) : null ); } } diff --git a/ext/view/main.php b/ext/view/main.php index 52e674fb26..0c7b2775d4 100644 --- a/ext/view/main.php +++ b/ext/view/main.php @@ -123,7 +123,7 @@ public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event) global $config; $image_info = $config->get_string(ImageConfig::INFO); if ($image_info) { - $event->add_part(SHM_POST_INFO("Info", false, $event->image->get_info()), 85); + $event->add_part(SHM_POST_INFO("Info", $event->image->get_info()), 85); } } } diff --git a/themes/rule34v2/tag_edit.theme.php b/themes/rule34v2/tag_edit.theme.php index d1a5138e1b..88725ce811 100644 --- a/themes/rule34v2/tag_edit.theme.php +++ b/themes/rule34v2/tag_edit.theme.php @@ -27,36 +27,31 @@ public function display_mass_editor() public function get_tag_editor_html(Image $image): HTMLElement { - $h_tags = html_escape($image->get_tag_list()); - return rawHTML(" - - Tags - - - - - "); + global $user; + return SHM_POST_INFO( + "Tags", + INPUT([ + "type" => "text", + "name" => "tag_edit__tags", + "value" => $image->get_tag_list(), + "autocomplete" => "off" + ]) + ); } public function get_source_editor_html(Image $image): HTMLElement { global $user; - $h_source = html_escape($image->get_source()); - $f_source = $this->format_source($image->get_source()); - $style = "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"; - return rawHTML(" - - Source Link - - ".($user->can("edit_image_source") ? " -
    $f_source
    - - " : " -
    $f_source
    - ")." - - - "); + return SHM_POST_INFO( + A(["href" => make_link("source_history/{$image->id}")], rawHTML("Source Link")), + emptyHTML( + DIV( + ["style" => "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"], + $this->format_source($image->get_source()) + ), + $user->can(Permissions::EDIT_IMAGE_SOURCE) ? INPUT(["type" => "text", "name" => "tag_edit__source", "value" => $image->get_source()]) : null + ) + ); } public function get_user_editor_html(Image $image): HTMLElement @@ -67,13 +62,12 @@ public function get_user_editor_html(Image $image): HTMLElement $ip = $user->can(Permissions::VIEW_IP) ? rawHTML(" (" . show_ip($image->owner_ip, "Post posted {$image->posted}") . ")") : ""; $info = SHM_POST_INFO( "Uploader", - $user->can(Permissions::EDIT_IMAGE_OWNER), emptyHTML( A(["class" => "username", "href" => make_link("user/$owner")], $owner), $ip, ", ", $date, - INPUT(["type" => "text", "name" => "tag_edit__owner", "value" => $owner]) + $user->can(Permissions::EDIT_IMAGE_OWNER) ? INPUT(["type" => "text", "name" => "tag_edit__owner", "value" => $owner]) : null ), ); // SHM_POST_INFO returns a TR, let's sneakily append @@ -92,11 +86,9 @@ public function get_lock_editor_html(Image $image): HTMLElement global $user; return SHM_POST_INFO( "Locked", - $user->can(Permissions::EDIT_IMAGE_LOCK), - emptyHTML( - INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()]), - $image->is_locked() ? "Yes (Only admins may edit these details)" : "No", - ), + $user->can(Permissions::EDIT_IMAGE_LOCK) ? + INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()]) : + emptyHTML($image->is_locked() ? "Yes (Only admins may edit these details)" : "No") ); } } From d489a4fa1853b1e1a1c10ecadfcf42679f2bceb5 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 19 Dec 2023 12:08:39 +0000 Subject: [PATCH 035/154] [user_config] remove redundant overrides --- ext/user_config/style.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ext/user_config/style.css b/ext/user_config/style.css index bc460fa4e6..c12e323b35 100644 --- a/ext/user_config/style.css +++ b/ext/user_config/style.css @@ -1,7 +1,5 @@ .setupblocks { column-width: 400px; - -moz-column-width: 400px; - -webkit-column-width: 400px; max-width: 1200px; margin: auto; } @@ -9,11 +7,7 @@ .setupblock { break-inside: avoid; - -moz-break-inside: avoid; - -webkit-break-inside: avoid; column-break-inside: avoid; - -moz-column-break-inside: avoid; - -webkit-column-break-inside: avoid; text-align: center; width: 90%; } From 4decd03898434e30d1d6a6b4610f54f8e3aaf072 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 19 Dec 2023 12:19:19 +0000 Subject: [PATCH 036/154] avoid duplicating tests when pushing to a PR branch --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f714292f0..43a213284b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,6 +2,9 @@ name: Tests on: push: + branches: + - main + - master pull_request: schedule: - cron: '0 2 * * 0' # Weekly on Sundays at 02:00 From a491d70b0eb170ff52a9a1a3be003a2908074700 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 21 Dec 2023 15:26:30 +0000 Subject: [PATCH 037/154] [ratings] don't crash if rating isn't set yet --- ext/rating/main.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/rating/main.php b/ext/rating/main.php index 9ad7741529..d02f5e27b9 100644 --- a/ext/rating/main.php +++ b/ext/rating/main.php @@ -225,7 +225,9 @@ public function onImageInfoSet(ImageInfoSetEvent $event) public function onParseLinkTemplate(ParseLinkTemplateEvent $event) { - $event->replace('$rating', $this->rating_to_human($event->image->rating)); + if(!is_null($event->image->rating)) { + $event->replace('$rating', $this->rating_to_human($event->image->rating)); + } } public function onHelpPageBuilding(HelpPageBuildingEvent $event) From ad181377c85cc979716cd8efefa1575aa3551b1d Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 21 Dec 2023 21:09:49 +0000 Subject: [PATCH 038/154] [userclass] ghosts should still be able to read PMs --- core/userclass.php | 1 + ext/pm/main.php | 8 ++++++-- ext/pm/theme.php | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/userclass.php b/core/userclass.php index 2473e9549f..bbc608639a 100644 --- a/core/userclass.php +++ b/core/userclass.php @@ -88,6 +88,7 @@ public function can(string $ability): bool // Ghost users can't do anything new UserClass("ghost", "base", [ + Permissions::READ_PM => true, ]); // Anonymous users can't do anything by default, but diff --git a/ext/pm/main.php b/ext/pm/main.php index 6f56761bcd..4ff47a684f 100644 --- a/ext/pm/main.php +++ b/ext/pm/main.php @@ -212,7 +212,7 @@ public function onUserPageBuilding(UserPageBuildingEvent $event) if (!is_null($pms)) { $this->theme->display_pms($page, $pms); } - if ($user->id != $duser->id) { + if ($user->can(Permissions::SEND_PM) && $user->id != $duser->id) { $this->theme->display_composer($page, $user, $duser); } } @@ -235,7 +235,11 @@ public function onPageRequest(PageRequestEvent $event) $database->execute("UPDATE private_message SET is_read=true WHERE id = :id", ["id" => $pm_id]); $cache->delete("pm-count-{$user->id}"); } - $this->theme->display_message($page, $from_user, $user, PM::from_row($pm)); + $pmo = PM::from_row($pm); + $this->theme->display_message($page, $from_user, $user, $pmo); + if($user->can(Permissions::SEND_PM)) { + $this->theme->display_composer($page, $user, $from_user, "Re: ".$pmo->subject); + } } else { $this->theme->display_permission_denied(); } diff --git a/ext/pm/theme.php b/ext/pm/theme.php index c1cf4d0643..c80e7f507b 100644 --- a/ext/pm/theme.php +++ b/ext/pm/theme.php @@ -81,7 +81,6 @@ public function display_composer(Page $page, User $from, User $to, $subject = "" public function display_message(Page $page, User $from, User $to, PM $pm) { - $this->display_composer($page, $to, $from, "Re: ".$pm->subject); $page->set_title("Private Message"); $page->set_heading(html_escape($pm->subject)); $page->add_block(new NavBlock()); From 8015d69acf501e9305e3128da7045229e11b1949 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 21 Dec 2023 15:51:27 +0000 Subject: [PATCH 039/154] [s3] extension for adding/deleting images on an S3 compatible host --- composer.json | 3 +- composer.lock | 2436 ++++++++++++++++++++++++--------------------- ext/s3/config.php | 13 + ext/s3/info.php | 17 + ext/s3/main.php | 148 +++ 5 files changed, 1506 insertions(+), 1111 deletions(-) create mode 100644 ext/s3/config.php create mode 100644 ext/s3/info.php create mode 100644 ext/s3/main.php diff --git a/composer.json b/composer.json index 4d12054dc1..da06083655 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,8 @@ "psr/simple-cache" : "^1.0", "sabre/cache" : "^2.0.1", "naroga/redis-cache": "dev-master", - "tbela99/css": "dev-master" + "tbela99/css": "dev-master", + "aws/aws-sdk-php": "^3.294" }, "require-dev" : { diff --git a/composer.lock b/composer.lock index 20ff5649fe..74d150694b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,157 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "422b41bfec446ff1062a14df333a8bbd", + "content-hash": "091b1bcf581b4d04a9989860b068f327", "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.2.4", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/eb0c6e4e142224a10b08f49ebf87f32611d162b2", + "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.4" + }, + "time": "2023-11-08T00:42:13+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.294.4", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "4f59bf50aa445fc3ec0b10648b205dd2465e9bec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4f59bf50aa445fc3ec0b10648b205dd2465e9bec", + "reference": "4f59bf50aa445fc3ec0b10648b205dd2465e9bec", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.2.3", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "mtdowling/jmespath.php": "^2.6", + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.294.4" + }, + "time": "2023-12-20T19:21:19+00:00" + }, { "name": "axy/backtrace", "version": "1.0.7", @@ -473,44 +622,56 @@ "time": "2023-02-20T17:27:30+00:00" }, { - "name": "ifixit/php-akismet", - "version": "1.1", - "source": { - "type": "git", - "url": "https://github.com/iFixit/php-akismet.git", - "reference": "fd4ff50eb577457c1b7b887401663e91e77625ae" - }, - "type": "library" - }, - { - "name": "naroga/redis-cache", - "version": "dev-master", + "name": "guzzlehttp/guzzle", + "version": "7.9.x-dev", "source": { "type": "git", - "url": "https://github.com/naroga/redis-cache.git", - "reference": "c32ee4ce2efcf8292cac6b6192c17c0306320d04" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/naroga/redis-cache/zipball/c32ee4ce2efcf8292cac6b6192c17c0306320d04", - "reference": "c32ee4ce2efcf8292cac6b6192c17c0306320d04", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { - "php": ">=5.3.3", - "predis/predis": "^1.1", - "psr/simple-cache": "~1.0" + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" }, "require-dev": { - "phpunit/php-code-coverage": ">=2.2.4", - "phpunit/phpunit": ">=3.7.38", - "satooshi/php-coveralls": ">=1.0.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" }, - "default-branch": true, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Naroga\\RedisCache\\": "." + "GuzzleHttp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -519,51 +680,105 @@ ], "authors": [ { - "name": "Pedro Cordeiro", - "email": "pedro.cordeiro@sympla.com.br" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "A Redis driver that implements PSR-16 (Simple Cache)", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], "support": { - "issues": "https://github.com/naroga/redis-cache/issues", - "source": "https://github.com/naroga/redis-cache/tree/1.2" + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, - "time": "2021-01-25T13:15:08+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:35:24+00:00" }, { - "name": "opis/closure", - "version": "dev-master", + "name": "guzzlehttp/promises", + "version": "2.0.x-dev", "source": { "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + "url": "https://github.com/guzzle/promises.git", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "default-branch": true, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { - "files": [ - "functions.php" - ], "psr-4": { - "Opis\\Closure\\": "src/" + "GuzzleHttp\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -572,58 +787,93 @@ ], "authors": [ { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", + "description": "Guzzle promises library", "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" + "promise" ], "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, - "time": "2022-01-27T09:35:39+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:19:20+00:00" }, { - "name": "predis/predis", - "version": "v1.x-dev", + "name": "guzzlehttp/psr7", + "version": "2.6.x-dev", "source": { "type": "git", - "url": "https://github.com/predis/predis.git", - "reference": "deee2b6d605eb6401446f6f6354414ab7571a5a0" + "url": "https://github.com/guzzle/psr7.git", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/deee2b6d605eb6401446f6f6354414ab7571a5a0", - "reference": "deee2b6d605eb6401446f6f6354414ab7571a5a0", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { - "ext-curl": "Allows access to Webdis when paired with phpiredis", - "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, + "default-branch": true, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { "psr-4": { - "Predis\\": "src/" + "GuzzleHttp\\Psr7\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -632,62 +882,120 @@ ], "authors": [ { - "name": "Daniele Alessandri", - "email": "suppakilla@gmail.com", - "homepage": "http://clorophilla.net", - "role": "Creator & Maintainer" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { - "name": "Till Krüss", - "homepage": "https://till.im", - "role": "Maintainer" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], - "description": "Flexible and feature-complete Redis client for PHP and HHVM", - "homepage": "http://github.com/predis/predis", + "description": "PSR-7 message implementation that also provides common utility methods", "keywords": [ - "nosql", - "predis", - "redis" + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" ], "support": { - "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v1.x" + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { - "url": "https://github.com/sponsors/tillkruss", + "url": "https://github.com/GrahamCampbell", "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" } ], - "time": "2023-09-19T16:11:21+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { - "name": "psr/simple-cache", - "version": "1.0.1", + "name": "ifixit/php-akismet", + "version": "1.1", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "url": "https://github.com/iFixit/php-akismet.git", + "reference": "fd4ff50eb577457c1b7b887401663e91e77625ae" + }, + "type": "library" + }, + { + "name": "mtdowling/jmespath.php", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "b243cacd2a9803b4cbc259246aa5081208238c10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/b243cacd2a9803b4cbc259246aa5081208238c10", + "reference": "b243cacd2a9803b4cbc259246aa5081208238c10", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, + "default-branch": true, + "bin": [ + "bin/jp.php" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.7-dev" } }, "autoload": { + "files": [ + "src/JmesPath.php" + ], "psr-4": { - "Psr\\SimpleCache\\": "src/" + "JmesPath\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -696,117 +1004,110 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" } ], - "description": "Common interfaces for simple caching", + "description": "Declaratively specify how to extract elements from a JSON document", "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" + "json", + "jsonpath" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/master" + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/master" }, - "time": "2017-10-23T01:57:42+00:00" + "time": "2023-11-30T16:26:47+00:00" }, { - "name": "sabre/cache", - "version": "2.0.1", + "name": "naroga/redis-cache", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/sabre-io/cache.git", - "reference": "a843741b85025d8674bf4713121cae60172e6f86" + "url": "https://github.com/naroga/redis-cache.git", + "reference": "c32ee4ce2efcf8292cac6b6192c17c0306320d04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabre-io/cache/zipball/a843741b85025d8674bf4713121cae60172e6f86", - "reference": "a843741b85025d8674bf4713121cae60172e6f86", + "url": "https://api.github.com/repos/naroga/redis-cache/zipball/c32ee4ce2efcf8292cac6b6192c17c0306320d04", + "reference": "c32ee4ce2efcf8292cac6b6192c17c0306320d04", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0", - "psr/simple-cache": "^1.0" - }, - "provide": { - "psr/simple-cache-implementation": "~1.0" + "php": ">=5.3.3", + "predis/predis": "^1.1", + "psr/simple-cache": "~1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.14.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-phpunit": "^1.3", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^9.6" + "phpunit/php-code-coverage": ">=2.2.4", + "phpunit/phpunit": ">=3.7.38", + "satooshi/php-coveralls": ">=1.0.1" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { - "Sabre\\Cache\\": "lib/" + "Naroga\\RedisCache\\": "." } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Evert Pot", - "email": "me@evertpot.com", - "homepage": "https://evertpot.com/", - "role": "Developer" + "name": "Pedro Cordeiro", + "email": "pedro.cordeiro@sympla.com.br" } ], - "description": "Simple cache abstraction layer implementing PSR-16", - "homepage": "http://sabre.io/dav/", - "keywords": [ - "apc", - "apcu", - "cache", - "memcache", - "memcached", - "psr-16", - "sabre", - "simple-cache" - ], + "description": "A Redis driver that implements PSR-16 (Simple Cache)", "support": { - "forum": "https://groups.google.com/group/sabredav-discuss", - "issues": "https://github.com/sabre-io/cache/issues", - "source": "https://github.com/fruux/sabre-skel" + "issues": "https://github.com/naroga/redis-cache/issues", + "source": "https://github.com/naroga/redis-cache/tree/1.2" }, - "time": "2023-02-09T23:47:10+00:00" + "time": "2021-01-25T13:15:08+00:00" }, { - "name": "shish/eventtracer-php", - "version": "v2.1.0", + "name": "opis/closure", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/shish/eventtracer-php.git", - "reference": "5dfe2c090c8b7df772e982520c36f44b33ead035" + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shish/eventtracer-php/zipball/5dfe2c090c8b7df772e982520c36f44b33ead035", - "reference": "5dfe2c090c8b7df772e982520c36f44b33ead035", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^8.0" + "php": "^5.4 || ^7.0 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.12", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.0" + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, + "default-branch": true, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -814,47 +1115,58 @@ ], "authors": [ { - "name": "Shish", - "email": "webmaster@shishnet.org", - "homepage": "http://shishnet.org", - "role": "Developer" + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" } ], - "description": "An API to write JSON traces as used by the Chrome Trace Viewer", - "homepage": "https://github.com/shish/eventtracer-php", + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], "support": { - "issues": "https://github.com/shish/eventtracer-php/issues", - "source": "https://github.com/shish/eventtracer-php/tree/v2.1.0" + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" }, - "time": "2023-02-04T12:26:41+00:00" + "time": "2022-01-27T09:35:39+00:00" }, { - "name": "shish/ffsphp", - "version": "v1.2.0", + "name": "predis/predis", + "version": "v1.x-dev", "source": { "type": "git", - "url": "https://github.com/shish/ffsphp.git", - "reference": "a2a1f0fe59606b90460513b48bacb442630dd253" + "url": "https://github.com/predis/predis.git", + "reference": "deee2b6d605eb6401446f6f6354414ab7571a5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shish/ffsphp/zipball/a2a1f0fe59606b90460513b48bacb442630dd253", - "reference": "a2a1f0fe59606b90460513b48bacb442630dd253", + "url": "https://api.github.com/repos/predis/predis/zipball/deee2b6d605eb6401446f6f6354414ab7571a5a0", + "reference": "deee2b6d605eb6401446f6f6354414ab7571a5a0", "shasum": "" }, "require": { - "ext-pdo": "*", - "php": "^8.0" + "php": ">=5.3.9" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.12", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" }, "type": "library", "autoload": { "psr-4": { - "FFSPHP\\": "src" + "Predis\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -863,48 +1175,64 @@ ], "authors": [ { - "name": "Shish", - "email": "webmaster@shishnet.org", - "homepage": "http://shishnet.org", - "role": "Developer" + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net", + "role": "Creator & Maintainer" + }, + { + "name": "Till Krüss", + "homepage": "https://till.im", + "role": "Maintainer" } ], - "description": "A collection of workarounds for stupid PHP things", - "homepage": "https://github.com/shish/ffsphp", + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/predis/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], "support": { - "issues": "https://github.com/shish/ffsphp/issues", - "source": "https://github.com/shish/ffsphp/tree/v1.2.0" + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v1.x" }, - "time": "2023-08-28T15:51:16+00:00" + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2023-09-19T16:11:21+00:00" }, { - "name": "shish/gqla", - "version": "dev-main", + "name": "psr/http-client", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/shish/gqla.git", - "reference": "26bf0405445f4e32e68cd7f1ea2256e81c6c42cb" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shish/gqla/zipball/26bf0405445f4e32e68cd7f1ea2256e81c6c42cb", - "reference": "26bf0405445f4e32e68cd7f1ea2256e81c6c42cb", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "php": "^8.1", - "webonyx/graphql-php": "^15.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.12", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.0" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "default-branch": true, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { "psr-4": { - "GQLA\\": "src" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -913,52 +1241,51 @@ ], "authors": [ { - "name": "Shish", - "email": "webmaster@shishnet.org", - "homepage": "https://shish.io", - "role": "Developer" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "A set of annotations for generating graphql APIs", - "homepage": "https://github.com/shish/gqla", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", "keywords": [ - "graphql" + "http", + "http-client", + "psr", + "psr-18" ], "support": { - "issues": "https://github.com/shish/gqla/issues", - "source": "https://github.com/shish/gqla/tree/main" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-03-03T00:12:44+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { - "name": "shish/microcrud", - "version": "v2.1.1", + "name": "psr/http-factory", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/shish/microcrud.git", - "reference": "8f0c38af61e2543c2e11ad6face37ae34e4571cf" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "7037f4b0950474e9d1350e8df89b15f1842085f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shish/microcrud/zipball/8f0c38af61e2543c2e11ad6face37ae34e4571cf", - "reference": "8f0c38af61e2543c2e11ad6face37ae34e4571cf", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/7037f4b0950474e9d1350e8df89b15f1842085f6", + "reference": "7037f4b0950474e9d1350e8df89b15f1842085f6", "shasum": "" }, "require": { - "ext-pdo": "*", - "php": "^8.0", - "shish/ffsphp": "^1.0", - "shish/microhtml": "^2.0.2" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.12", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.0" + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" }, + "default-branch": true, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { "psr-4": { - "MicroCRUD\\": "src" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -967,51 +1294,54 @@ ], "authors": [ { - "name": "Shish", - "email": "webmaster@shishnet.org", - "homepage": "http://shishnet.org", - "role": "Developer" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "A minimal CRUD generating library", - "homepage": "https://github.com/shish/microcrud", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ - "crud", - "generator" + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" ], "support": { - "issues": "https://github.com/shish/microcrud/issues", - "source": "https://github.com/shish/microcrud/tree/v2.1.1" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-11-11T21:25:53+00:00" + "time": "2023-09-22T11:16:44+00:00" }, { - "name": "shish/microhtml", - "version": "v2.2.1", + "name": "psr/http-message", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/shish/microhtml.git", - "reference": "824d8541c7f0662e26d03d65d865d8f13ea57a72" + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shish/microhtml/zipball/824d8541c7f0662e26d03d65d865d8f13ea57a72", - "reference": "824d8541c7f0662e26d03d65d865d8f13ea57a72", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": "^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.12", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.0" + "php": "^7.2 || ^8.0" }, + "default-branch": true, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { - "files": [ - "src/microhtml.php" - ] + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1019,23 +1349,595 @@ ], "authors": [ { - "name": "Shish", - "email": "webmaster@shishnet.org", - "homepage": "http://shishnet.org", - "role": "Developer" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "A minimal HTML generating library", - "homepage": "https://github.com/shish/microhtml", + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ - "generator", - "html" + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "sabre/cache", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/cache.git", + "reference": "a843741b85025d8674bf4713121cae60172e6f86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/cache/zipball/a843741b85025d8674bf4713121cae60172e6f86", + "reference": "a843741b85025d8674bf4713121cae60172e6f86", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/simple-cache": "^1.0" + }, + "provide": { + "psr/simple-cache-implementation": "~1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.14.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^9.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sabre\\Cache\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "https://evertpot.com/", + "role": "Developer" + } + ], + "description": "Simple cache abstraction layer implementing PSR-16", + "homepage": "http://sabre.io/dav/", + "keywords": [ + "apc", + "apcu", + "cache", + "memcache", + "memcached", + "psr-16", + "sabre", + "simple-cache" + ], + "support": { + "forum": "https://groups.google.com/group/sabredav-discuss", + "issues": "https://github.com/sabre-io/cache/issues", + "source": "https://github.com/fruux/sabre-skel" + }, + "time": "2023-02-09T23:47:10+00:00" + }, + { + "name": "shish/eventtracer-php", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/shish/eventtracer-php.git", + "reference": "5dfe2c090c8b7df772e982520c36f44b33ead035" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shish/eventtracer-php/zipball/5dfe2c090c8b7df772e982520c36f44b33ead035", + "reference": "5dfe2c090c8b7df772e982520c36f44b33ead035", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.12", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shish", + "email": "webmaster@shishnet.org", + "homepage": "http://shishnet.org", + "role": "Developer" + } + ], + "description": "An API to write JSON traces as used by the Chrome Trace Viewer", + "homepage": "https://github.com/shish/eventtracer-php", + "support": { + "issues": "https://github.com/shish/eventtracer-php/issues", + "source": "https://github.com/shish/eventtracer-php/tree/v2.1.0" + }, + "time": "2023-02-04T12:26:41+00:00" + }, + { + "name": "shish/ffsphp", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/shish/ffsphp.git", + "reference": "a2a1f0fe59606b90460513b48bacb442630dd253" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shish/ffsphp/zipball/a2a1f0fe59606b90460513b48bacb442630dd253", + "reference": "a2a1f0fe59606b90460513b48bacb442630dd253", + "shasum": "" + }, + "require": { + "ext-pdo": "*", + "php": "^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.12", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "FFSPHP\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shish", + "email": "webmaster@shishnet.org", + "homepage": "http://shishnet.org", + "role": "Developer" + } + ], + "description": "A collection of workarounds for stupid PHP things", + "homepage": "https://github.com/shish/ffsphp", + "support": { + "issues": "https://github.com/shish/ffsphp/issues", + "source": "https://github.com/shish/ffsphp/tree/v1.2.0" + }, + "time": "2023-08-28T15:51:16+00:00" + }, + { + "name": "shish/gqla", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/shish/gqla.git", + "reference": "26bf0405445f4e32e68cd7f1ea2256e81c6c42cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shish/gqla/zipball/26bf0405445f4e32e68cd7f1ea2256e81c6c42cb", + "reference": "26bf0405445f4e32e68cd7f1ea2256e81c6c42cb", + "shasum": "" + }, + "require": { + "php": "^8.1", + "webonyx/graphql-php": "^15.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.12", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.0" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "GQLA\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shish", + "email": "webmaster@shishnet.org", + "homepage": "https://shish.io", + "role": "Developer" + } + ], + "description": "A set of annotations for generating graphql APIs", + "homepage": "https://github.com/shish/gqla", + "keywords": [ + "graphql" + ], + "support": { + "issues": "https://github.com/shish/gqla/issues", + "source": "https://github.com/shish/gqla/tree/main" + }, + "time": "2023-03-03T00:12:44+00:00" + }, + { + "name": "shish/microcrud", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/shish/microcrud.git", + "reference": "8f0c38af61e2543c2e11ad6face37ae34e4571cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shish/microcrud/zipball/8f0c38af61e2543c2e11ad6face37ae34e4571cf", + "reference": "8f0c38af61e2543c2e11ad6face37ae34e4571cf", + "shasum": "" + }, + "require": { + "ext-pdo": "*", + "php": "^8.0", + "shish/ffsphp": "^1.0", + "shish/microhtml": "^2.0.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.12", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "MicroCRUD\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shish", + "email": "webmaster@shishnet.org", + "homepage": "http://shishnet.org", + "role": "Developer" + } + ], + "description": "A minimal CRUD generating library", + "homepage": "https://github.com/shish/microcrud", + "keywords": [ + "crud", + "generator" + ], + "support": { + "issues": "https://github.com/shish/microcrud/issues", + "source": "https://github.com/shish/microcrud/tree/v2.1.1" + }, + "time": "2023-11-11T21:25:53+00:00" + }, + { + "name": "shish/microhtml", + "version": "v2.2.1", + "source": { + "type": "git", + "url": "https://github.com/shish/microhtml.git", + "reference": "824d8541c7f0662e26d03d65d865d8f13ea57a72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shish/microhtml/zipball/824d8541c7f0662e26d03d65d865d8f13ea57a72", + "reference": "824d8541c7f0662e26d03d65d865d8f13ea57a72", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.12", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/microhtml.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shish", + "email": "webmaster@shishnet.org", + "homepage": "http://shishnet.org", + "role": "Developer" + } + ], + "description": "A minimal HTML generating library", + "homepage": "https://github.com/shish/microhtml", + "keywords": [ + "generator", + "html" ], "support": { "issues": "https://github.com/shish/microhtml/issues", "source": "https://github.com/shish/microhtml/tree/v2.2.1" }, - "time": "2023-08-17T16:39:06+00:00" + "time": "2023-08-17T16:39:06+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" }, { "name": "tbela99/css", @@ -1476,312 +2378,54 @@ }, { "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.x" - }, - "time": "2023-08-23T17:36:07+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "2.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e", - "reference": "6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^12", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.4" - }, - "default-branch": true, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2023-12-09T14:19:21+00:00" - }, - { - "name": "doctrine/lexer", - "version": "3.1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "0d54c073afb397d5896df60cc34170cf37dfad5e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/0d54c073afb397d5896df60cc34170cf37dfad5e", - "reference": "0d54c073afb397d5896df60cc34170cf37dfad5e", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.1.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2023-07-05T07:23:35+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.41.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "8b6ae8dcbaf23f09680643ab832a4a3a260265f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/8b6ae8dcbaf23f09680643ab832a4a3a260265f6", - "reference": "8b6ae8dcbaf23f09680643ab832a4a3a260265f6", - "shasum": "" - }, - "require": { - "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.3", - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0", - "sebastian/diff": "^4.0 || ^5.0", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", - "symfony/finder": "^5.4 || ^6.0 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", - "symfony/polyfill-mbstring": "^1.28", - "symfony/polyfill-php80": "^1.28", - "symfony/polyfill-php81": "^1.28", - "symfony/process": "^5.4 || ^6.0 || ^7.0", - "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" - }, - "require-dev": { - "facile-it/paraunit": "^1.3 || ^2.0", - "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^2.1", - "mikey179/vfsstream": "^1.6.11", - "php-coveralls/php-coveralls": "^2.7", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", - "phpunit/phpunit": "^9.6", - "symfony/phpunit-bridge": "^6.3.8 || ^7.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz RumiÅ„ski", - "email": "dariusz.ruminski@gmail.com" + "email": "schmittjoh@gmail.com" } ], - "description": "A tool to automatically fix PHP code style", + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", "keywords": [ - "Static code analysis", - "fixer", - "standards", - "static analysis" + "annotations", + "docblock", + "parser" ], "support": { - "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.41.1" + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.x" }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2023-12-10T19:59:27+00:00" + "time": "2023-08-23T17:36:07+00:00" }, { - "name": "guzzlehttp/guzzle", - "version": "7.9.x-dev", + "name": "doctrine/instantiator", + "version": "2.0.x-dev", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "url": "https://github.com/doctrine/instantiator.git", + "reference": "6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e", + "reference": "6c0ee619435c5d4f3bc515ab1514cf4cf1006c6e", "shasum": "" }, "require": { - "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", - "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" + "php": "^8.1" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", - "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", - "psr/log": "^1.1 || ^2.0 || ^3.0" - }, - "suggest": { - "ext-curl": "Required for CURL handler support", - "ext-intl": "Required for Internationalized Domain Name (IDN) support", - "psr/log": "Required for using the Log middleware" + "doctrine/coding-standard": "^12", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.4" }, + "default-branch": true, "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { - "GuzzleHttp\\": "src/" + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1790,105 +2434,65 @@ ], "authors": [ { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" } ], - "description": "Guzzle is a PHP HTTP client library", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "psr-18", - "psr-7", - "rest", - "web service" + "constructor", + "instantiate" ], "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.x" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", - "type": "github" + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" }, { - "url": "https://github.com/Nyholm", - "type": "github" + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" }, { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2023-12-09T14:19:21+00:00" }, { - "name": "guzzlehttp/promises", - "version": "2.0.x-dev", + "name": "doctrine/lexer", + "version": "3.1.x-dev", "source": { "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "url": "https://github.com/doctrine/lexer.git", + "reference": "0d54c073afb397d5896df60cc34170cf37dfad5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/0d54c073afb397d5896df60cc34170cf37dfad5e", + "reference": "0d54c073afb397d5896df60cc34170cf37dfad5e", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^8.1" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.0" }, - "default-branch": true, "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, "autoload": { "psr-4": { - "GuzzleHttp\\Promise\\": "src/" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1897,93 +2501,103 @@ ], "authors": [ { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" }, { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" + "name": "Roman Borschel", + "email": "roman@code-factory.org" }, { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" } ], - "description": "Guzzle promises library", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", "keywords": [ - "promise" + "annotations", + "docblock", + "lexer", + "parser", + "php" ], "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.1.x" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", - "type": "github" + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" }, { - "url": "https://github.com/Nyholm", - "type": "github" + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" }, { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2023-07-05T07:23:35+00:00" }, { - "name": "guzzlehttp/psr7", - "version": "2.6.x-dev", + "name": "friendsofphp/php-cs-fixer", + "version": "v3.41.1", "source": { "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "8b6ae8dcbaf23f09680643ab832a4a3a260265f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/8b6ae8dcbaf23f09680643ab832a4a3a260265f6", + "reference": "8b6ae8dcbaf23f09680643ab832a4a3a260265f6", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0 || ^5.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "facile-it/paraunit": "^1.3 || ^2.0", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", + "phpunit/phpunit": "^9.6", + "symfony/phpunit-bridge": "^6.3.8 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" - }, - "default-branch": true, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", "autoload": { "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" + "PhpCsFixer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1992,71 +2606,32 @@ ], "authors": [ { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" + "name": "Dariusz RumiÅ„ski", + "email": "dariusz.ruminski@gmail.com" } ], - "description": "PSR-7 message implementation that also provides common utility methods", + "description": "A tool to automatically fix PHP code style", "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" + "Static code analysis", + "fixer", + "standards", + "static analysis" ], "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.41.1" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", + "url": "https://github.com/keradus", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2023-12-10T19:59:27+00:00" }, { "name": "jms/metadata", @@ -3136,180 +3711,19 @@ "reference": "707984727bd5b2b670e59559d3ed2500240cf875", "shasum": "" }, - "require": { - "php": ">=7.4.0" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container" - }, - "time": "2023-09-22T11:11:30+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "977ffcf551e3bfb73d90aac3e8e1583fd8d2f89a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/977ffcf551e3bfb73d90aac3e8e1583fd8d2f89a", - "reference": "977ffcf551e3bfb73d90aac3e8e1583fd8d2f89a", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "suggest": { - "fig/event-dispatcher-util": "Provides some useful PSR-14 utilities" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "source": "https://github.com/php-fig/event-dispatcher" - }, - "time": "2023-09-22T11:10:57+00:00" - }, - { - "name": "psr/http-client", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client" - }, - "time": "2023-09-23T14:17:50+00:00" - }, - { - "name": "psr/http-factory", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "7037f4b0950474e9d1350e8df89b15f1842085f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/7037f4b0950474e9d1350e8df89b15f1842085f6", - "reference": "7037f4b0950474e9d1350e8df89b15f1842085f6", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0 || ^2.0" + "require": { + "php": ">=7.4.0" }, "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3322,49 +3736,51 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], "support": { - "source": "https://github.com/php-fig/http-factory" + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container" }, - "time": "2023-09-22T11:16:44+00:00" + "time": "2023-09-22T11:11:30+00:00" }, { - "name": "psr/http-message", + "name": "psr/event-dispatcher", "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "977ffcf551e3bfb73d90aac3e8e1583fd8d2f89a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/977ffcf551e3bfb73d90aac3e8e1583fd8d2f89a", + "reference": "977ffcf551e3bfb73d90aac3e8e1583fd8d2f89a", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=7.2.0" + }, + "suggest": { + "fig/event-dispatcher-util": "Provides some useful PSR-14 utilities" }, "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "Psr\\EventDispatcher\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3377,20 +3793,16 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", + "description": "Standard interfaces for event handling.", "keywords": [ - "http", - "http-message", + "events", "psr", - "psr-7", - "request", - "response" + "psr-14" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" + "source": "https://github.com/php-fig/event-dispatcher" }, - "time": "2023-04-04T09:54:51+00:00" + "time": "2023-09-22T11:10:57+00:00" }, { "name": "psr/log", @@ -3443,50 +3855,6 @@ }, "time": "2021-07-14T16:46:02+00:00" }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, { "name": "scrutinizer/ocular", "version": "1.9", @@ -4564,74 +4932,6 @@ ], "time": "2023-12-10T16:15:48+00:00" }, - { - "name": "symfony/deprecation-contracts", - "version": "dev-main", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-05-23T14:45:45+00:00" - }, { "name": "symfony/event-dispatcher", "version": "6.4.x-dev", @@ -5233,90 +5533,6 @@ ], "time": "2023-01-26T09:26:14+00:00" }, - { - "name": "symfony/polyfill-mbstring", - "version": "1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-28T09:04:16+00:00" - }, { "name": "symfony/polyfill-php80", "version": "1.x-dev", @@ -5843,5 +6059,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/ext/s3/config.php b/ext/s3/config.php new file mode 100644 index 0000000000..818f2a3534 --- /dev/null +++ b/ext/s3/config.php @@ -0,0 +1,13 @@ + self::SHISH_EMAIL]; + public string $license = self::LICENSE_GPLV2; + public string $description = "Push post updates to S3"; +} diff --git a/ext/s3/main.php b/ext/s3/main.php new file mode 100644 index 0000000000..f127de371c --- /dev/null +++ b/ext/s3/main.php @@ -0,0 +1,148 @@ +panel->create_new_block("S3 CDN"); + $sb->add_text_option(S3Config::ACCESS_KEY_ID, "Access Key ID: "); + $sb->add_text_option(S3Config::ACCESS_KEY_SECRET, "
    Access Key Secret: "); + $sb->add_text_option(S3Config::ENDPOINT, "
    Endpoint: "); + $sb->add_text_option(S3Config::IMAGE_BUCKET, "
    Image Bucket: "); + } + + public function onCommand(CommandEvent $event) + { + if ($event->cmd == "help") { + print "\ts3-sync \n"; + print "\t\tsync a post to s3\n\n"; + print "\ts3-rm \n"; + print "\t\tdelete a leftover file from s3\n\n"; + } + if ($event->cmd == "s3-sync") { + if (preg_match('/^(\d+)-(\d+)$/', $event->args[0], $matches)) { + $start = (int)$matches[1]; + $end = (int)$matches[2]; + } else { + $start = (int)$event->args[0]; + $end = $start; + } + foreach(Search::find_images_iterable(tags: ["order=id", "id>=$start", "id<=$end"]) as $image) { + print("{$image->id}: {$image->hash}\n"); + ob_flush(); + $this->sync_post($image); + } + } + if ($event->cmd == "s3-rm") { + foreach($event->args as $hash) { + print("{$hash}\n"); + ob_flush(); + $this->remove_file($hash); + } + } + } + + public function onImageAddition(ImageAdditionEvent $event) + { + $this->sync_post($event->image); + } + + public function onTagSet(TagSetEvent $event) + { + // pretend that tags were set already so that sync works + $orig_tags = $event->image->tag_array; + $event->image->tag_array = $event->tags; + $this->sync_post($event->image); + $event->image->tag_array = $orig_tags; + } + + public function onImageDeletion(ImageDeletionEvent $event) + { + $this->remove_file($event->image->hash); + } + + public function onImageReplace(ImageReplaceEvent $event) + { + $existing = Image::by_id($event->id); + $this->remove_file($existing->hash); + $this->sync_post($event->image); + } + + // utils + private function get_client() + { + global $config; + $access_key_id = $config->get_string(S3Config::ACCESS_KEY_ID); + $access_key_secret = $config->get_string(S3Config::ACCESS_KEY_SECRET); + if(is_null($access_key_id) || is_null($access_key_secret)) { + return null; + } + $endpoint = $config->get_string(S3Config::ENDPOINT); + $credentials = new \Aws\Credentials\Credentials($access_key_id, $access_key_secret); + return new \Aws\S3\S3Client([ + 'region' => 'auto', + 'endpoint' => $endpoint, + 'version' => 'latest', + 'credentials' => $credentials, + ]); + } + + private function hash_to_path(string $hash) + { + $ha = substr($hash, 0, 2); + $sh = substr($hash, 2, 2); + return "$ha/$sh/$hash"; + } + + // underlying s3 interaction functions + private function sync_post(Image $image) + { + global $config; + + // multiple events can trigger a sync, + // let's only do one per request + if(in_array($image->id, self::$synced)) { + return; + } + self::$synced[] = $image->id; + + $client = $this->get_client(); + if(is_null($client)) { + return; + } + $image_bucket = $config->get_string(S3Config::IMAGE_BUCKET); + $friendly = $image->parse_link_template('$id - $tags.$ext'); + $client->putObject([ + 'Bucket' => $image_bucket, + 'Key' => $this->hash_to_path($image->hash), + 'Body' => file_get_contents($image->get_image_filename()), + 'ACL' => 'public-read', + 'ContentType' => $image->get_mime(), + 'ContentDisposition' => "inline; filename=\"$friendly\"", + ]); + } + + private function remove_file(string $hash) + { + global $config; + $client = $this->get_client(); + if(is_null($client)) { + return; + } + $image_bucket = $config->get_string(S3Config::IMAGE_BUCKET); + $client->deleteObject([ + 'Bucket' => $image_bucket, + 'Key' => $this->hash_to_path($hash), + ]); + } +} From 56a238e4216eada26fe483b61e5d10ed7d9cfa1b Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 23 Dec 2023 00:55:07 +0000 Subject: [PATCH 040/154] [docker] add php-curl --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e7a0b74dd3..261cdfdc1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN curl --output /usr/share/keyrings/nginx-keyring.gpg https://unit.nginx.org/k RUN echo 'deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bookworm unit' > /etc/apt/sources.list.d/unit.list RUN apt update && apt install -y \ php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \ - php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 \ + php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 php${PHP_VERSION}-curl \ gosu curl imagemagick ffmpeg zip unzip git unit unit-php gettext procps net-tools # Composer has 100MB of dependencies, and we only need that during build and test From 6212566aeaa10d749a6f0d60ac2bebcdedb888b6 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 23 Dec 2023 01:12:48 +0000 Subject: [PATCH 041/154] [docker] make exec friendlier --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 261cdfdc1e..14a868dec8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ RUN echo 'deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages RUN apt update && apt install -y \ php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \ php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 php${PHP_VERSION}-curl \ - gosu curl imagemagick ffmpeg zip unzip git unit unit-php gettext procps net-tools + curl imagemagick ffmpeg zip unzip git unit unit-php gettext procps net-tools vim # Composer has 100MB of dependencies, and we only need that during build and test FROM base AS composer @@ -50,5 +50,6 @@ EXPOSE 8000 HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 ENV UID=1000 GID=1000 UPLOAD_MAX_FILESIZE=50M COPY --from=build /app /app +WORKDIR /app ENTRYPOINT ["/app/.docker/entrypoint.sh"] CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"] From 8b31fea14e0f54d3feb49214eef227b7aabea5a4 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 13:46:59 +0000 Subject: [PATCH 042/154] fix downvote link typo --- ext/numeric_score/main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php index 61b0b0c305..028eca6944 100644 --- a/ext/numeric_score/main.php +++ b/ext/numeric_score/main.php @@ -122,7 +122,7 @@ public function onUserPageBuilding(UserPageBuildingEvent $event) $n_up = Search::count_images(["upvoted_by={$event->display_user->name}"]); $link_up = search_link(["upvoted_by={$event->display_user->name}"]); $n_down = Search::count_images(["downvoted_by={$event->display_user->name}"]); - $link_down = search_link(["downvoted_by={$event->display_user->name}]"]); + $link_down = search_link(["downvoted_by={$event->display_user->name}"]); $event->add_stats("$n_up Upvotes / $n_down Downvotes"); } From 7f02a77ecb1cf0dec519d9528c0a8e1e8e576bf0 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 15:50:54 +0000 Subject: [PATCH 043/154] [docker] flatten build a bit --- Dockerfile | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 14a868dec8..d25fe0ec7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,18 +2,22 @@ ARG PHP_VERSION=8.2 # Install base packages which all stages (build, test, run) need FROM debian:bookworm AS base -RUN apt update && apt upgrade -y -RUN apt update && apt install -y curl -RUN curl --output /usr/share/keyrings/nginx-keyring.gpg https://unit.nginx.org/keys/nginx-keyring.gpg -RUN echo 'deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bookworm unit' > /etc/apt/sources.list.d/unit.list -RUN apt update && apt install -y \ +RUN apt update && \ + apt upgrade -y && \ + apt install -y curl && \ + curl --output /usr/share/keyrings/nginx-keyring.gpg https://unit.nginx.org/keys/nginx-keyring.gpg && \ + echo 'deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bookworm unit' > /etc/apt/sources.list.d/unit.list && \ + apt update && apt install -y --no-install-recommends \ php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \ php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 php${PHP_VERSION}-curl \ - curl imagemagick ffmpeg zip unzip git unit unit-php gettext procps net-tools vim + curl imagemagick ffmpeg zip unzip git unit unit-php gettext && \ + rm -rf /var/lib/apt/lists/* # Composer has 100MB of dependencies, and we only need that during build and test FROM base AS composer -RUN apt update && apt upgrade -y && apt install -y composer php${PHP_VERSION}-xdebug && rm -rf /var/lib/apt/lists/* +RUN apt update && apt upgrade -y && \ + apt install -y composer php${PHP_VERSION}-xdebug procps net-tools vim && \ + rm -rf /var/lib/apt/lists/* ENV XDEBUG_MODE=coverage # "Build" shimmie (composer install - done in its own stage so that we don't From 79299398f5dd3788827aa2da490c2542641493df Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 16:30:19 +0000 Subject: [PATCH 044/154] [docker] static ffmpeg to save 300MB of dependencies --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d25fe0ec7b..5c41801c74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ ARG PHP_VERSION=8.2 # Install base packages which all stages (build, test, run) need FROM debian:bookworm AS base +COPY --from=mwader/static-ffmpeg:6.1 /ffmpeg /usr/local/bin/ RUN apt update && \ apt upgrade -y && \ apt install -y curl && \ @@ -10,7 +11,7 @@ RUN apt update && \ apt update && apt install -y --no-install-recommends \ php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \ php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 php${PHP_VERSION}-curl \ - curl imagemagick ffmpeg zip unzip git unit unit-php gettext && \ + curl imagemagick zip unzip git unit unit-php gettext && \ rm -rf /var/lib/apt/lists/* # Composer has 100MB of dependencies, and we only need that during build and test From be353450d8b1a39ae789ef6a8453fdb34f8751da Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 18:55:44 +0000 Subject: [PATCH 045/154] [docker] prod-mode doesn't need git, which means we don't need perl, which saves another 100MB --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5c41801c74..2cc0f5f8d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,13 +11,13 @@ RUN apt update && \ apt update && apt install -y --no-install-recommends \ php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \ php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 php${PHP_VERSION}-curl \ - curl imagemagick zip unzip git unit unit-php gettext && \ + curl imagemagick zip unzip unit unit-php gettext && \ rm -rf /var/lib/apt/lists/* # Composer has 100MB of dependencies, and we only need that during build and test FROM base AS composer RUN apt update && apt upgrade -y && \ - apt install -y composer php${PHP_VERSION}-xdebug procps net-tools vim && \ + apt install -y composer php${PHP_VERSION}-xdebug git procps net-tools vim && \ rm -rf /var/lib/apt/lists/* ENV XDEBUG_MODE=coverage From 2ac243d20d86f15e755aec813c2685230138ad07 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 21:12:59 +0000 Subject: [PATCH 046/154] [core] use rawurlencode/decode consistently, deprecate caret system --- core/event.php | 30 ++++++++++++++++++-- core/imageboard/misc.php | 2 +- core/imageboard/tag.php | 49 --------------------------------- core/tests/TagTest.php | 14 ---------- core/tests/UrlsTest.php | 2 +- core/urls.php | 2 +- ext/index/theme.php | 4 +-- ext/rss_images/main.php | 2 +- ext/view/main.php | 4 +-- ext/view/theme.php | 2 +- themes/danbooru/index.theme.php | 2 +- 11 files changed, 37 insertions(+), 76 deletions(-) diff --git a/core/event.php b/core/event.php index c1eec6d6f2..464f601d36 100644 --- a/core/event.php +++ b/core/event.php @@ -103,7 +103,7 @@ public function get_arg(int $n): string { $offset = $this->part_count + $n; if ($offset >= 0 && $offset < $this->arg_count) { - return $this->args[$offset]; + return rawurldecode($this->args[$offset]); } else { $nm1 = $this->arg_count - 1; throw new UserErrorException("Requested an invalid page argument {$offset} / {$nm1}"); @@ -144,7 +144,33 @@ public function get_search_terms(): array { $search_terms = []; if ($this->count_args() === 2) { - $search_terms = Tag::explode(Tag::decaret($this->get_arg(0))); + $str = $this->get_arg(0); + + // decode legacy caret-encoding just in case + // somebody has bookmarked such a URL + $from_caret = [ + "^" => "^", + "s" => "/", + "b" => "\\", + "q" => "?", + "a" => "&", + "d" => ".", + ]; + $out = ""; + $length = strlen($str); + for ($i = 0; $i < $length; $i++) { + if ($str[$i] == "^") { + $i++; + $out .= $from_caret[$str[$i]] ?? ''; + } else { + $out .= $str[$i]; + } + } + $str = $out; + // end legacy + + + $search_terms = Tag::explode($str); } return $search_terms; } diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index 2fdc82a820..6a96baeeca 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -187,7 +187,7 @@ function redirect_to_next_image(Image $image): void global $page; if (isset($_GET['search'])) { - $search_terms = Tag::explode(Tag::decaret($_GET['search'])); + $search_terms = Tag::explode($_GET['search']); $query = "search=" . url_escape($_GET['search']); } else { $search_terms = []; diff --git a/core/imageboard/tag.php b/core/imageboard/tag.php index 46bfc07071..6d4b5d6005 100644 --- a/core/imageboard/tag.php +++ b/core/imageboard/tag.php @@ -273,53 +273,4 @@ public static function sqlify(string $term): string // $term = str_replace("?", "_", $term); return $term; } - - /** - * Kind of like urlencode, but using a custom scheme so that - * tags always fit neatly between slashes in a URL. Use this - * when you want to put an arbitrary tag into a URL. - */ - public static function caret(string $input): string - { - $to_caret = [ - "^" => "^", - "/" => "s", - "\\" => "b", - "?" => "q", - "&" => "a", - "." => "d", - ]; - - foreach ($to_caret as $from => $to) { - $input = str_replace($from, '^' . $to, $input); - } - return $input; - } - - /** - * Use this when you want to get a tag out of a URL - */ - public static function decaret(string $str): string - { - $from_caret = [ - "^" => "^", - "s" => "/", - "b" => "\\", - "q" => "?", - "a" => "&", - "d" => ".", - ]; - - $out = ""; - $length = strlen($str); - for ($i = 0; $i < $length; $i++) { - if ($str[$i] == "^") { - $i++; - $out .= $from_caret[$str[$i]] ?? ''; - } else { - $out .= $str[$i]; - } - } - return $out; - } } diff --git a/core/tests/TagTest.php b/core/tests/TagTest.php index 1fdbcdf401..de92889858 100644 --- a/core/tests/TagTest.php +++ b/core/tests/TagTest.php @@ -10,20 +10,6 @@ class TagTest extends TestCase { - public function test_caret() - { - $this->assertEquals("foo", Tag::decaret("foo")); - $this->assertEquals("foo?", Tag::decaret("foo^q")); - $this->assertEquals("a^b/c\\d?e&f", Tag::decaret("a^^b^sc^bd^qe^af")); - } - - public function test_decaret() - { - $this->assertEquals("foo", Tag::caret("foo")); - $this->assertEquals("foo^q", Tag::caret("foo?")); - $this->assertEquals("a^^b^sc^bd^qe^af", Tag::caret("a^b/c\\d?e&f")); - } - public function test_compare() { $this->assertFalse(Tag::compare(["foo"], ["bar"])); diff --git a/core/tests/UrlsTest.php b/core/tests/UrlsTest.php index 59ab6342f4..2d8c8e101e 100644 --- a/core/tests/UrlsTest.php +++ b/core/tests/UrlsTest.php @@ -17,7 +17,7 @@ public function test_search_link() search_link(["foo", "bar"]) ); $this->assertEquals( - "/test/post/list/cat%2A%20rating%3D%5Eq/1", + "/test/post/list/cat%2A%20rating%3D%3F/1", search_link(["rating=?", "cat*"]) ); } diff --git a/core/urls.php b/core/urls.php index 66fc3cf8b9..845873e90b 100644 --- a/core/urls.php +++ b/core/urls.php @@ -28,7 +28,7 @@ public function make_link(): string function search_link(array $terms = [], int $page = 1): string { if($terms) { - $q = rawurlencode(Tag::caret(Tag::implode($terms))); + $q = url_escape(Tag::implode($terms)); return make_link("post/list/$q/$page"); } else { return make_link("post/list/$page"); diff --git a/ext/index/theme.php b/ext/index/theme.php index 6093c7a13e..17b353507e 100644 --- a/ext/index/theme.php +++ b/ext/index/theme.php @@ -78,8 +78,6 @@ protected function build_navigation(int $page_number, int $total_pages, array $s $prev = $page_number - 1; $next = $page_number + 1; - $u_tags = url_escape(Tag::implode($search_terms)); - $h_prev = ($page_number <= 1) ? "Prev" : 'Prev'; $h_index = "Index"; $h_next = ($page_number >= $total_pages) ? "Next" : 'Next'; @@ -176,7 +174,7 @@ protected function display_page_images(Page $page, array $images) // only index the first pages of each term $page->add_html_header(''); } - $query = url_escape(Tag::caret(Tag::implode($this->search_terms))); + $query = url_escape(Tag::implode($this->search_terms)); $page->add_block(new Block("Posts", $this->build_table($images, "#search=$query"), "main", 10, "image-list")); $this->display_paginator($page, "post/list/$query", null, $this->page_number, $this->total_pages, true); } else { diff --git a/ext/rss_images/main.php b/ext/rss_images/main.php index 2bc29c1854..8d2ff3529a 100644 --- a/ext/rss_images/main.php +++ b/ext/rss_images/main.php @@ -12,7 +12,7 @@ public function onPostListBuilding(PostListBuildingEvent $event) $title = $config->get_string(SetupConfig::TITLE); if (count($event->search_terms) > 0) { - $search = url_escape(Tag::caret(Tag::implode($event->search_terms))); + $search = url_escape(Tag::implode($event->search_terms)); $page->add_html_header(""); } else { diff --git a/ext/view/main.php b/ext/view/main.php index 0c7b2775d4..fd9852d6a4 100644 --- a/ext/view/main.php +++ b/ext/view/main.php @@ -22,11 +22,11 @@ public function onPageRequest(PageRequestEvent $event) { global $page, $user; - if ($event->page_matches("post/prev") || $event->page_matches("post/next")) { + if ($event->page_matches("post/prev") || $event->page_matches("post/next")) { $image_id = int_escape($event->get_arg(0)); if (isset($_GET['search'])) { - $search_terms = Tag::explode(Tag::decaret($_GET['search'])); + $search_terms = Tag::explode($_GET['search']); $query = "#search=".url_escape($_GET['search']); } else { $search_terms = []; diff --git a/ext/view/theme.php b/ext/view/theme.php index f41b17d61f..d1bc2d70ea 100644 --- a/ext/view/theme.php +++ b/ext/view/theme.php @@ -45,7 +45,7 @@ public function display_admin_block(Page $page, $parts) protected function get_query(): ?string { if (isset($_GET['search'])) { - $query = "search=".url_escape(Tag::caret($_GET['search'])); + $query = "search=".url_escape($_GET['search']); } else { $query = null; } diff --git a/themes/danbooru/index.theme.php b/themes/danbooru/index.theme.php index 9a24cc1b75..20e32b386f 100644 --- a/themes/danbooru/index.theme.php +++ b/themes/danbooru/index.theme.php @@ -20,7 +20,7 @@ public function display_page(Page $page, array $images) $page_title = $config->get_string(SetupConfig::TITLE); } else { $search_string = Tag::implode($this->search_terms); - $query = url_escape(Tag::caret($search_string)); + $query = url_escape($search_string); $page_title = html_escape($search_string); } From cf9d6ebfa0bef1f891a105363b652d56e6f1ab42 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 22:49:50 +0000 Subject: [PATCH 047/154] [docker] disable healthcheck traefik won't send traffic to containers that are "starting" (pre first health check) - it insists on waiting for the full minute until after the once-per-minute check has passed :/ --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2cc0f5f8d0..25b835ffa9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ EXPOSE 8000 # Actually run shimmie FROM base AS run EXPOSE 8000 -HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 +# HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 ENV UID=1000 GID=1000 UPLOAD_MAX_FILESIZE=50M COPY --from=build /app /app WORKDIR /app From 89dfd332909ba99748a8c313e028b9a0ffbff701 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 23:06:26 +0000 Subject: [PATCH 048/154] allow tags to wrap --- ext/static_files/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/static_files/style.css b/ext/static_files/style.css index ded6fe8417..7ca6389be8 100644 --- a/ext/static_files/style.css +++ b/ext/static_files/style.css @@ -32,3 +32,7 @@ IMG.lazy {display: none;} margin: 8px; border: 1px solid #882; } + +.tag { + overflow-wrap: anywhere; +} \ No newline at end of file From ce962cb358fbc66b224c219821719fc341d69e1e Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 23:16:46 +0000 Subject: [PATCH 049/154] [style] keep form elements wide even if in a span --- ext/static_files/style.css | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ext/static_files/style.css b/ext/static_files/style.css index 7ca6389be8..b83b60f65b 100644 --- a/ext/static_files/style.css +++ b/ext/static_files/style.css @@ -1,14 +1,15 @@ ARTICLE SELECT {width: 150px;} INPUT, TEXTAREA {box-sizing: border-box;} -TD>INPUT[type="button"], -TD>INPUT[type="submit"], -TD>INPUT[type="text"], -TD>INPUT[type="password"], -TD>INPUT[type="email"], -TD>SELECT, -TD>TEXTAREA, -TD>BUTTON {width: 100%;} + +TD>INPUT[type="button"], TD>SPAN>INPUT[type="button"], +TD>INPUT[type="submit"], TD>SPAN>INPUT[type="submit"], +TD>INPUT[type="text"], TD>SPAN>INPUT[type="text"], +TD>INPUT[type="password"], TD>SPAN>INPUT[type="password"], +TD>INPUT[type="email"], TD>SPAN>INPUT[type="email"], +TD>SELECT, TD>SPAN>SELECT, +TD>TEXTAREA, TD>SPAN>TEXTAREA, +TD>BUTTON, TD>SPAN>BUTTON {width: 100%;} TABLE.form {width: 300px;} TABLE.form TD, TABLE.form TH {vertical-align: middle;} From 1f4d2a5b3e32d3d8ad60ed42472b5881afaef74b Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 25 Dec 2023 10:39:37 +0000 Subject: [PATCH 050/154] [user_config] remove duplicate style file --- ext/user_config/style.css | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 ext/user_config/style.css diff --git a/ext/user_config/style.css b/ext/user_config/style.css deleted file mode 100644 index c12e323b35..0000000000 --- a/ext/user_config/style.css +++ /dev/null @@ -1,37 +0,0 @@ -.setupblocks { - column-width: 400px; - max-width: 1200px; - margin: auto; -} -.setupblocks > .setupblock:first-of-type { margin-top: 0; } - -.setupblock { - break-inside: avoid; - column-break-inside: avoid; - text-align: center; - width: 90%; -} -.setupblock TEXTAREA { - width: 100%; - font-size: 0.75em; - resize: vertical; -} - -.helpable { - border-bottom: 1px dashed gray; -} - -.ok { - background: #AFA; -} -.bad { - background: #FAA; -} - -#Setupmain .blockbody { - background: none; - border: none; - box-shadow: none; - margin: 0; - padding: 0; -} From 75a7b793805ee7736a69f8a1b0c7735d97ee25c5 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 25 Dec 2023 13:08:14 +0000 Subject: [PATCH 051/154] [themes] set font-size on :root rather than BODY so that REMs work consistently --- themes/default/style.css | 6 ++++-- themes/futaba/style.css | 6 ++++-- themes/lite/style.css | 6 ++++-- themes/rule34v2/style.css | 6 ++++-- themes/warm/style.css | 6 ++++-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/themes/default/style.css b/themes/default/style.css index 3ff7a787cd..4f00b290ad 100644 --- a/themes/default/style.css +++ b/themes/default/style.css @@ -45,12 +45,14 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * things common to all pages * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +:root { + font-size: 14px; + font-family: sans-serif; +} BODY { background: var(--page); color: var(--text); - font-family: sans-serif; - font-size: 14px; margin: 0; } H1 { diff --git a/themes/futaba/style.css b/themes/futaba/style.css index e0cf705d68..cb647ec7bf 100644 --- a/themes/futaba/style.css +++ b/themes/futaba/style.css @@ -2,10 +2,12 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * things common to all pages * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -BODY { +:root { font-family: arial,helvetica,sans-serif; font-size: 10pt; +} + +BODY { background: #FFFFEE url(fade.png) top center repeat-x; color: #800000; padding-left: 5px; diff --git a/themes/lite/style.css b/themes/lite/style.css index 0d8fced9ff..77f0d6eccd 100644 --- a/themes/lite/style.css +++ b/themes/lite/style.css @@ -3,10 +3,12 @@ * http://qwebirc.org/ */ -BODY { - background: #F0F7FF; +:root { font-family: sans-serif; font-size: 14px; +} +BODY { + background: #F0F7FF; margin: 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * diff --git a/themes/rule34v2/style.css b/themes/rule34v2/style.css index 94481eb7ce..45b7daa3b0 100644 --- a/themes/rule34v2/style.css +++ b/themes/rule34v2/style.css @@ -3,10 +3,12 @@ * things common to all pages * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -BODY { - background: url(bg.png) #ACE4A3; +:root { font-family: "Arial", sans-serif; font-size: 14px; +} +BODY { + background: url(bg.png) #ACE4A3; margin: 0; } #header { diff --git a/themes/warm/style.css b/themes/warm/style.css index 616010eea3..ba29557145 100644 --- a/themes/warm/style.css +++ b/themes/warm/style.css @@ -3,10 +3,12 @@ * things common to all pages * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -BODY { - background: url(bg.png); +:root { font-family: "Arial", sans-serif; font-size: 14px; +} +BODY { + background: url(bg.png); margin: 0; } HEADER { From 6d370b0aa32d7d8a98e56e629c77c04ff38cf00f Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 25 Dec 2023 15:33:57 +0000 Subject: [PATCH 052/154] [all themes] use rems for more consistent font sizing --- ext/bbcode/style.css | 2 +- ext/downtime/theme.php | 2 +- ext/handle_cbz/style.css | 2 +- ext/home/style.css | 4 ++-- ext/rule34/style.css | 2 +- ext/setup/style.css | 2 +- themes/default/style.css | 4 ++-- themes/futaba/style.css | 6 +++--- themes/lite/style.css | 17 ++++------------- themes/rule34v2/header.inc | 2 +- themes/rule34v2/home.theme.php | 4 ++-- themes/rule34v2/menuh.css | 2 +- themes/rule34v2/style.css | 25 ++++++++----------------- themes/rule34v2/upload.theme.php | 2 +- themes/warm/style.css | 17 ++++------------- 15 files changed, 33 insertions(+), 60 deletions(-) diff --git a/ext/bbcode/style.css b/ext/bbcode/style.css index 200221cc20..32c257a12b 100644 --- a/ext/bbcode/style.css +++ b/ext/bbcode/style.css @@ -1,7 +1,7 @@ CODE { background: #DEDEDE; - font-size: 0.8em; + font-size: 0.8rem; } BLOCKQUOTE { border: 1px solid black; diff --git a/ext/downtime/theme.php b/ext/downtime/theme.php index 6876eaf6bc..1a21d9aee9 100644 --- a/ext/downtime/theme.php +++ b/ext/downtime/theme.php @@ -13,7 +13,7 @@ public function display_notification(Page $page) { $page->add_block(new Block( "Downtime", - "DOWNTIME MODE IS ON!", + "DOWNTIME MODE IS ON!", "left", 0 )); diff --git a/ext/handle_cbz/style.css b/ext/handle_cbz/style.css index 4813925cc6..bb9dbd34ec 100644 --- a/ext/handle_cbz/style.css +++ b/ext/handle_cbz/style.css @@ -1,7 +1,7 @@ #comicMain { background: black; color: white; - font-size: 3em; + font-size: 3rem; } #comicPageList { width: 90%; diff --git a/ext/home/style.css b/ext/home/style.css index 234f512d86..c1c9b78a5d 100644 --- a/ext/home/style.css +++ b/ext/home/style.css @@ -1,10 +1,10 @@ -div#front-page h1 {font-size: 4em; margin-top: 2em; margin-bottom: 0; text-align: center; border: none; background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;} +div#front-page h1 {font-size: 4rem; margin-top: 2em; margin-bottom: 0; text-align: center; border: none; background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;} div#front-page {text-align:center;} .space {margin-bottom: 1em;} div#front-page div#links a {margin: 0 0.5em;} div#front-page li {list-style-type: none; margin: 0;} @media (max-width: 800px) { - div#front-page h1 {font-size: 3em; margin-top: 0.5em; margin-bottom: 0.5em;} + div#front-page h1 {font-size: 3rem; margin-top: 0.5em; margin-bottom: 0.5em;} #counter {display: none;} } diff --git a/ext/rule34/style.css b/ext/rule34/style.css index 2e394c2ddc..13aca22b57 100644 --- a/ext/rule34/style.css +++ b/ext/rule34/style.css @@ -20,7 +20,7 @@ BODY.censored FOOTER { left: 20%; right: 20%; text-align: center; - font-size: 2em; + font-size: 2rem; background: #ACE4A3; border: 1px solid #7EB977; z-index: 9999999999999999999999; diff --git a/ext/setup/style.css b/ext/setup/style.css index c12e323b35..6237cd6501 100644 --- a/ext/setup/style.css +++ b/ext/setup/style.css @@ -13,7 +13,7 @@ } .setupblock TEXTAREA { width: 100%; - font-size: 0.75em; + font-size: 0.75rem; resize: vertical; } diff --git a/themes/default/style.css b/themes/default/style.css index 4f00b290ad..4fad4fff55 100644 --- a/themes/default/style.css +++ b/themes/default/style.css @@ -99,7 +99,7 @@ TABLE.zebra TR:nth-child(even) {background: var(--zebra-even);} FOOTER { clear: both; - font-size: 0.7em; + font-size: 0.7rem; text-align: center; background: var(--title); border: 1px solid var(--title-border); @@ -131,7 +131,7 @@ NAV { margin-left: 16px; } NAV .blockbody { - font-size: 0.85em; + font-size: 0.85rem; text-align: center; overflow: hidden; } diff --git a/themes/futaba/style.css b/themes/futaba/style.css index cb647ec7bf..3d07afa8c3 100644 --- a/themes/futaba/style.css +++ b/themes/futaba/style.css @@ -26,7 +26,7 @@ H1 { FOOTER { clear: both; padding-top: 8px; - font-size: 0.7em; + font-size: 0.7rem; text-align: center; } @@ -45,7 +45,7 @@ NAV { margin-left: 16px; } NAV .blockbody { - font-size: 0.85em; + font-size: 0.85rem; text-align: center; overflow: hidden; } @@ -115,7 +115,7 @@ TABLE.tag_list>TBODY>TR>TD:after { #short-wiki-description > .blockbody { padding-bottom: 15px; - font-size: 1.1em; + font-size: 1.1rem; } #short-wiki-description h2 { margin: 0 0 0.4em; diff --git a/themes/lite/style.css b/themes/lite/style.css index 77f0d6eccd..7ee8564513 100644 --- a/themes/lite/style.css +++ b/themes/lite/style.css @@ -128,16 +128,7 @@ TD { } CODE { background: #DEDEDE; - font-size: 0.8em; -} -#subtitle { - width: 256px; - font-size: 0.75em; - margin: -16px auto auto; - text-align: center; - border: 1px solid black; - border-top: none; - background: #DDD; + font-size: 0.8rem; } TABLE.zebra {border-spacing: 0; border: 2px solid #C3D2E0;} @@ -166,7 +157,7 @@ INPUT:hover, button:hover, TEXTAREA:hover { FOOTER { clear: both; padding: 8px; - font-size: 0.7em; + font-size: 0.7rem; text-align: center; border-top: 1px solid #C3D2E0; background: #E3EFFA; @@ -196,7 +187,7 @@ NAV { margin-left: 16px; } NAV .blockbody { - font-size: 0.85em; + font-size: 0.85rem; text-align: center; } NAV TABLE { @@ -322,7 +313,7 @@ ARTICLE TABLE { } .setupblock TEXTAREA { width: 300px; - font-size: 0.75em; + font-size: 0.75rem; } .helpable { diff --git a/themes/rule34v2/header.inc b/themes/rule34v2/header.inc index f60c8704f1..33d3dfa4e0 100644 --- a/themes/rule34v2/header.inc +++ b/themes/rule34v2/header.inc @@ -8,7 +8,7 @@
    -  Sidebar  +  Sidebar  logo diff --git a/themes/rule34v2/home.theme.php b/themes/rule34v2/home.theme.php index 065c56f947..f1798a6fbe 100644 --- a/themes/rule34v2/home.theme.php +++ b/themes/rule34v2/home.theme.php @@ -21,13 +21,13 @@ public function display_page(Page $page, string $sitename, string $base_href, st $hh diff --git a/themes/rule34v2/menuh.css b/themes/rule34v2/menuh.css index d55e587195..1fb96d80fd 100644 --- a/themes/rule34v2/menuh.css +++ b/themes/rule34v2/menuh.css @@ -10,7 +10,7 @@ a:active.menu { color: #FF0000; text-decoration: none; } #menuh-container { - font-size: 1em; + font-size: 1rem; float: left; top:0; left: 5%; diff --git a/themes/rule34v2/style.css b/themes/rule34v2/style.css index 45b7daa3b0..b2403a3c6c 100644 --- a/themes/rule34v2/style.css +++ b/themes/rule34v2/style.css @@ -20,7 +20,7 @@ BODY { text-align: center; } H1 { - font-size: 5em; + font-size: 5rem; margin: 0; padding: 0; } @@ -39,15 +39,6 @@ TD { text-align: center; } -#subtitle { - width: 256px; - font-size: 0.75em; - margin: -16px auto auto; - text-align: center; - border: 1px solid black; - border-top: none; - background: #DDD; -} #flash { background: #FF7; display: block; @@ -57,14 +48,14 @@ TD { } TABLE.zebra {background: #ACE4A3; border-collapse: collapse; border: 1px solid #7EB977;} -TABLE.zebra TD {font-size: 0.8em;margin: 0; border-top: 1px solid #7EB977; padding: 2px;} +TABLE.zebra TD {font-size: 0.8rem;margin: 0; border-top: 1px solid #7EB977; padding: 2px;} TABLE.zebra TR:nth-child(odd) {background: #9CD493;} TABLE.zebra TR:nth-child(even) {background: #ACE4A3;} FOOTER { clear: both; padding: 8px; - font-size: 0.7em; + font-size: 0.7rem; text-align: center; border-top: 1px solid #7EB977; background: #ACE4A3; @@ -96,7 +87,7 @@ NAV { margin-left: 16px; } NAV .blockbody { - font-size: 0.85em; + font-size: 0.85rem; text-align: center; } NAV TABLE { @@ -236,7 +227,7 @@ div#twitter_update_list li { vertical-align: middle; } #bans INPUT { - font-size: 0.85em; + font-size: 0.85rem; } .need-del { @@ -276,7 +267,7 @@ ul.tagit li.tagit-new { @media (max-width: 750px) { .atoz, #paginator { - font-size: 2em; + font-size: 2rem; } .header-sites { display: none; @@ -342,7 +333,7 @@ ul.tagit li.tagit-new { #UserBlockhead {display: block;} #Loginleft {display: none;} #Loginhead {display: block;} -.headcol {width: 250px; font-size: 0.85em;} +.headcol {width: 250px; font-size: 0.85rem;} .headbox {width: 80%; margin: auto;} #big-logo {display: table-cell;} #mini-logo {display: none;} @@ -360,7 +351,7 @@ ul.tagit li.tagit-new { /* hide nav-search when header-search is sticky */ ARTICLE {margin-top: 0;} - #Navigationleft .blockbody {font-size: 1.5em;} + #Navigationleft .blockbody {font-size: 1.5rem;} #Navigationleft .blockbody P, #Navigationleft .blockbody FORM {display: none;} diff --git a/themes/rule34v2/upload.theme.php b/themes/rule34v2/upload.theme.php index bf4ccb7088..8a278743e9 100644 --- a/themes/rule34v2/upload.theme.php +++ b/themes/rule34v2/upload.theme.php @@ -79,7 +79,7 @@ public function display_page(Page $page): void protected function build_upload_block(): HTMLElement { - return A(["href" => make_link("upload"), "style" => 'font-size: 2em; display: block;'], "Upload"); + return A(["href" => make_link("upload"), "style" => 'font-size: 2rem; display: block;'], "Upload"); } protected function h_upload_list_1(): HTMLElement diff --git a/themes/warm/style.css b/themes/warm/style.css index ba29557145..894ea9d364 100644 --- a/themes/warm/style.css +++ b/themes/warm/style.css @@ -20,7 +20,7 @@ HEADER { text-align: center; } H1 { - font-size: 5em; + font-size: 5rem; margin: 0; padding: 0; } @@ -40,16 +40,7 @@ TD { } CODE { background: #DEDEDE; - font-size: 0.8em; -} -#subtitle { - width: 256px; - font-size: 0.75em; - margin: -16px auto auto; - text-align: center; - border: 1px solid black; - border-top: none; - background: #DDD; + font-size: 0.8rem; } TABLE.zebra {border-spacing: 0; border: 1px solid #B89F7C; } @@ -63,7 +54,7 @@ TABLE.zebra TR:nth-child(even) {background: #DABC92;} FOOTER { clear: both; padding: 8px; - font-size: 0.7em; + font-size: 0.7rem; text-align: center; border-top: 1px solid #B89F7C; background: #FCD9A9; @@ -100,7 +91,7 @@ NAV { margin-left: 16px; } NAV .blockbody { - font-size: 0.85em; + font-size: 0.85rem; text-align: center; } NAV TABLE { From 532dbdc75118efb5d1026ed899a990df395d74c0 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 02:15:42 +0000 Subject: [PATCH 053/154] [themes] inline background image --- themes/rule34v2/bg.png | Bin 145 -> 0 bytes themes/rule34v2/style.css | 12 +----------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 themes/rule34v2/bg.png diff --git a/themes/rule34v2/bg.png b/themes/rule34v2/bg.png deleted file mode 100644 index 45e1b42c0a1c035c32141c136f71e2f29837284b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^{2voo}c)DoCv&E~P zE?)h3(V8cVD}OJQ1S&B1ba4#fn3?QRoakK$BoaJ|O}qvDicJy<1UU;!N(DI+3Q9Z$ p3yVuU6B5F_4ZN6zcuazM7;fE=ZD@7>`v7PJgQu&X%Q~loCII2aFPH!T diff --git a/themes/rule34v2/style.css b/themes/rule34v2/style.css index b2403a3c6c..035b9b24f4 100644 --- a/themes/rule34v2/style.css +++ b/themes/rule34v2/style.css @@ -8,7 +8,7 @@ font-size: 14px; } BODY { - background: url(bg.png) #ACE4A3; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPBAMAAADJ+Ih5AAAAFVBMVEV8und+uXeo5aKo5qOq5aOr46Ks5KN5+6UZAAAAN0lEQVQImWNIc2FLcUhzYWBIczRLEU5zNGBwEQlxdHURCWBwdEkRcXN0SWBgVkswSgMSDDRTDADa2B2AhUf94AAAAABJRU5ErkJggg=='); margin: 0; } #header { @@ -209,16 +209,6 @@ SECTION>H3 { padding: 4px; } -div#twitter_update_list li { - list-style:none; - padding-bottom:0; - text-align:left; - margin-top:5px; - margin-bottom:5px; - border: solid 1px #000; - background: url(bg.png); -} - .username { font-weight: bold; } From e5c8bf7b18515770eadf5e686f15ee72c3a746b8 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 02:36:51 +0000 Subject: [PATCH 054/154] one cookie set function, for consistent samesite/path/expires --- ext/blotter/script.js | 6 +++--- ext/handle_pixel/script.js | 8 ++++---- ext/index/script.js | 6 +++--- ext/pools/script.js | 2 +- ext/rule34/script.js | 4 ++-- ext/static_files/script.js | 11 +++++++++-- themes/lite/setup.theme.php | 6 +++--- themes/lite/user_config.theme.php | 6 +++--- themes/rule34v2/script.js | 12 ++++++------ 9 files changed, 34 insertions(+), 27 deletions(-) diff --git a/ext/blotter/script.js b/ext/blotter/script.js index 2623f3e45c..664d8588ec 100644 --- a/ext/blotter/script.js +++ b/ext/blotter/script.js @@ -4,14 +4,14 @@ document.addEventListener('DOMContentLoaded', () => { $(".shm-blotter2-toggle").click(function() { $(".shm-blotter2").slideToggle("slow", function() { if($(".shm-blotter2").is(":hidden")) { - Cookies.set("ui-blotter2-hidden", 'true'); + shm_cookie_set("ui-blotter2-hidden", 'true'); } else { - Cookies.set("ui-blotter2-hidden", 'false'); + shm_cookie_set("ui-blotter2-hidden", 'false'); } }); }); - if(Cookies.get("ui-blotter2-hidden") === 'true') { + if(shm_cookie_get("ui-blotter2-hidden") === 'true') { $(".shm-blotter2").hide(); } }); diff --git a/ext/handle_pixel/script.js b/ext/handle_pixel/script.js index 6cd83fbf3d..d2d6284f45 100644 --- a/ext/handle_pixel/script.js +++ b/ext/handle_pixel/script.js @@ -24,7 +24,7 @@ document.addEventListener('DOMContentLoaded', () => { $(".shm-zoomer").val(zoom_type); if (save_cookie) { - Cookies.set("ui-image-zoom", zoom_type, {expires: 365}); + shm_cookie_set("ui-image-zoom", zoom_type, {expires: 365, samesite: "lax", path: "/"}); } } @@ -38,13 +38,13 @@ document.addEventListener('DOMContentLoaded', () => { }); $("img.shm-main-image").click(function(e) { - switch(Cookies.get("ui-image-zoom")) { + switch(shm_cookie_get("ui-image-zoom")) { case "full": zoom("width"); break; default: zoom("full"); break; } }); - if(Cookies.get("ui-image-zoom")) { - zoom(Cookies.get("ui-image-zoom")); + if(shm_cookie_get("ui-image-zoom")) { + zoom(shm_cookie_get("ui-image-zoom")); } }); diff --git a/ext/index/script.js b/ext/index/script.js index 0f17ce9391..f2b4b02d8a 100644 --- a/ext/index/script.js +++ b/ext/index/script.js @@ -1,7 +1,7 @@ /*jshint bitwise:false, curly:true, eqeqeq:true, evil:true, forin:false, noarg:true, noempty:true, nonew:true, undef:false, strict:false, browser:true, jquery:true */ document.addEventListener('DOMContentLoaded', () => { - var blocked_tags = (Cookies.get("ui-blocked-tags") || "").split(" "); + var blocked_tags = (shm_cookie_get("ui-blocked-tags") || "").split(" "); var needs_refresh = false; for(var i=0; i { }); function select_blocked_tags() { - var blocked_tags = prompt("Enter tags to ignore", Cookies.get("ui-blocked-tags") || "AI-generated"); + var blocked_tags = prompt("Enter tags to ignore", shm_cookie_get("ui-blocked-tags") || "AI-generated"); if(blocked_tags !== null) { - Cookies.set("ui-blocked-tags", blocked_tags.toLowerCase(), {expires: 365}); + shm_cookie_set("ui-blocked-tags", blocked_tags.toLowerCase()); location.reload(true); } } diff --git a/ext/pools/script.js b/ext/pools/script.js index 16119d3285..19b8ea9ad0 100644 --- a/ext/pools/script.js +++ b/ext/pools/script.js @@ -3,7 +3,7 @@ document.addEventListener('DOMContentLoaded', () => { $('#order_pool').change(function(){ var val = $("#order_pool option:selected").val(); - Cookies.set("shm_ui-order-pool", val, {path: '/', expires: 365}); //FIXME: This won't play nice if COOKIE_PREFIX is not "shm_". + shm_cookie_set("shm_ui-order-pool", val); //FIXME: This won't play nice if COOKIE_PREFIX is not "shm_". window.location.href = ''; }); }); diff --git a/ext/rule34/script.js b/ext/rule34/script.js index 9d22a5e691..c4afbe0dd7 100644 --- a/ext/rule34/script.js +++ b/ext/rule34/script.js @@ -1,5 +1,5 @@ document.addEventListener('DOMContentLoaded', () => { - if(Cookies.get("ui-tnc-agreed") !== "true" && window.location.href.indexOf("/wiki/") == -1) { + if(shm_cookie_get("ui-tnc-agreed") !== "true" && window.location.href.indexOf("/wiki/") == -1) { $("BODY").addClass("censored"); $("BODY").append("
    "); $("BODY").append(""+ @@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', () => { }); function tnc_agree() { - Cookies.set("ui-tnc-agreed", "true", {path: '/', expires: 365}); + shm_cookie_set("ui-tnc-agreed", "true"); $("BODY").removeClass("censored"); $(".tnc_bg").hide(); $(".tnc").hide(); diff --git a/ext/static_files/script.js b/ext/static_files/script.js index a2a8097ef1..61eb16b339 100644 --- a/ext/static_files/script.js +++ b/ext/static_files/script.js @@ -1,5 +1,12 @@ /*jshint bitwise:false, curly:true, eqeqeq:true, evil:true, forin:false, noarg:true, noempty:true, nonew:true, undef:false, strict:false, browser:true */ +function shm_cookie_set(name, value) { + Cookies.set(name, value, {expires: 365, samesite: "lax", path: "/"}); +} +function shm_cookie_get(name) { + return Cookies.get(name); +} + document.addEventListener('DOMContentLoaded', () => { /** Load jQuery extensions **/ //Code via: https://stackoverflow.com/a/13106698 @@ -32,7 +39,7 @@ document.addEventListener('DOMContentLoaded', () => { /** Setup sidebar toggle **/ let sidebar_hidden = []; try { - sidebar_hidden = (Cookies.get("ui-sidebar-hidden") || "").split("|"); + sidebar_hidden = (shm_cookie_get("ui-sidebar-hidden") || "").split("|"); for (let i=0; i 0) { $(sidebar_hidden[i]+" .blockbody").hide(); @@ -55,7 +62,7 @@ document.addEventListener('DOMContentLoaded', () => { } } } - Cookies.set("ui-sidebar-hidden", sidebar_hidden.join("|"), {expires: 365}); + shm_cookie_set("ui-sidebar-hidden", sidebar_hidden.join("|")); }); }); diff --git a/themes/lite/setup.theme.php b/themes/lite/setup.theme.php index 2629eacd43..5aa3824e82 100644 --- a/themes/lite/setup.theme.php +++ b/themes/lite/setup.theme.php @@ -23,14 +23,14 @@ protected function sb_to_html(SetupBlock $block): string $(\"#$i-toggle\").click(function() { $(\"#$i\").slideToggle(\"slow\", function() { if($(\"#$i\").is(\":hidden\")) { - Cookies.set(\"$i-hidden\", 'true', {path: '/'}); + shm_cookie_set(\"$i-hidden\", 'true'); } else { - Cookies.set(\"$i-hidden\", 'false', {path: '/'}); + shm_cookie_set(\"$i-hidden\", 'false'); } }); }); - if(Cookies.get(\"$i-hidden\") == 'true') { + if(shm_cookie_get(\"$i-hidden\") == 'true') { $(\"#$i\").hide(); } }); diff --git a/themes/lite/user_config.theme.php b/themes/lite/user_config.theme.php index 2bd8c01d00..2ad74f7868 100644 --- a/themes/lite/user_config.theme.php +++ b/themes/lite/user_config.theme.php @@ -23,14 +23,14 @@ protected function sb_to_html(SetupBlock $block): string $(\"#$i-toggle\").click(function() { $(\"#$i\").slideToggle(\"slow\", function() { if($(\"#$i\").is(\":hidden\")) { - Cookies.set(\"$i-hidden\", 'true', {path: '/'}); + shm_cookie_set(\"$i-hidden\", 'true'); } else { - Cookies.set(\"$i-hidden\", 'false', {path: '/'}); + shm_cookie_set(\"$i-hidden\", 'false'); } }); }); - if(Cookies.get(\"$i-hidden\") == 'true') { + if(shm_cookie_get(\"$i-hidden\") == 'true') { $(\"#$i\").hide(); } }); diff --git a/themes/rule34v2/script.js b/themes/rule34v2/script.js index 8c559ad7c2..c58c4fab06 100644 --- a/themes/rule34v2/script.js +++ b/themes/rule34v2/script.js @@ -8,17 +8,17 @@ var navHidden = false; function toggleNav() { if(navHidden) { $('BODY').removeClass('navHidden'); - Cookies.set("ui-shownav", "true"); + shm_cookie_set("ui-shownav", "true"); } else { $('BODY').addClass('navHidden'); - Cookies.set("ui-shownav", "false"); + shm_cookie_set("ui-shownav", "false"); } navHidden = !navHidden; } document.addEventListener('DOMContentLoaded', () => { - if(Cookies.get("ui-shownav") === "false") { + if(shm_cookie_get("ui-shownav") === "false") { toggleNav(); } }); @@ -29,12 +29,12 @@ function toggleDesktop() { if(forceDesktop) { let viewport = document.querySelector("meta[name=viewport]"); viewport.setAttribute('content', 'width=512'); - Cookies.set("ui-desktop", "false"); + shm_cookie_set("ui-desktop", "false"); } else { let viewport = document.querySelector("meta[name=viewport]"); viewport.setAttribute('content', 'width=1024, initial-scale=0.4'); - Cookies.set("ui-desktop", "true"); + shm_cookie_set("ui-desktop", "true"); navHidden = true; toggleNav(); } @@ -42,7 +42,7 @@ function toggleDesktop() { } document.addEventListener('DOMContentLoaded', () => { - if(Cookies.get("ui-desktop") === "true") { + if(shm_cookie_get("ui-desktop") === "true") { toggleDesktop(); } }); From 85b1e54904795613aa5483e470838cbc849ca625 Mon Sep 17 00:00:00 2001 From: myname Date: Thu, 16 Nov 2023 22:10:14 -0600 Subject: [PATCH 055/154] Refactoring auto-complete functionality for alias editor and auto tagger --- core/imageboard/autocomplete_column.php | 34 +++++++++++++++++++++++++ ext/alias_editor/main.php | 4 +-- ext/auto_tagger/main.php | 4 +-- 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 core/imageboard/autocomplete_column.php diff --git a/core/imageboard/autocomplete_column.php b/core/imageboard/autocomplete_column.php new file mode 100644 index 0000000000..50ce8fcadf --- /dev/null +++ b/core/imageboard/autocomplete_column.php @@ -0,0 +1,34 @@ + "text", + "name" => "r_{$this->name}", + "class" => "autocomplete_tags", + "placeholder" => $this->title, + "value" => @$inputs["r_{$this->name}"] + ]); + } + + public function create_input(array $inputs) + { + return INPUT([ + "type" => "text", + "name" => "c_{$this->name}", + "class" => "autocomplete_tags", + "placeholder" => $this->title, + "value" => @$inputs["c_{$this->name}"] + ]); + } +} diff --git a/ext/alias_editor/main.php b/ext/alias_editor/main.php index 25632f2297..c4c9784259 100644 --- a/ext/alias_editor/main.php +++ b/ext/alias_editor/main.php @@ -19,8 +19,8 @@ public function __construct(\FFSPHP\PDO $db) $this->size = 100; $this->limit = 1000000; $this->set_columns([ - new TextColumn("oldtag", "Old Tag"), - new TextColumn("newtag", "New Tag"), + new AutoCompleteColumn("oldtag", "Old Tag"), + new AutoCompleteColumn("newtag", "New Tag"), new ActionColumn("oldtag"), ]); $this->order_by = ["oldtag"]; diff --git a/ext/auto_tagger/main.php b/ext/auto_tagger/main.php index a20084575a..c9a4b707f1 100644 --- a/ext/auto_tagger/main.php +++ b/ext/auto_tagger/main.php @@ -21,8 +21,8 @@ public function __construct(\FFSPHP\PDO $db) $this->size = 100; $this->limit = 1000000; $this->set_columns([ - new TextColumn("tag", "Tag"), - new TextColumn("additional_tags", "Additional Tags"), + new AutoCompleteColumn("tag", "Tag"), + new AutoCompleteColumn("additional_tags", "Additional Tags"), new ActionColumn("tag"), ]); $this->order_by = ["tag"]; From b55b5a0a0ff543d41cfd7687c3a6bccd4b55ee00 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 24 Dec 2023 21:21:25 +0000 Subject: [PATCH 056/154] [autocomplete] new from-scratch autocomplete implementation --- ext/autocomplete/lib/jquery-ui.min.css | 7 - ext/autocomplete/lib/jquery-ui.min.js | 13 - ext/autocomplete/lib/jquery-ui.theme.min.css | 5 - ext/autocomplete/lib/jquery.tagit.css | 69 ----- ext/autocomplete/lib/tag-it.min.js | 18 -- ext/autocomplete/lib/tagit.ui-zendesk.css | 97 ------ ext/autocomplete/main.php | 5 - ext/autocomplete/script.js | 308 ++++++++++++------- ext/autocomplete/style.css | 22 +- ext/autocomplete/theme.php | 19 -- 10 files changed, 220 insertions(+), 343 deletions(-) delete mode 100644 ext/autocomplete/lib/jquery-ui.min.css delete mode 100644 ext/autocomplete/lib/jquery-ui.min.js delete mode 100644 ext/autocomplete/lib/jquery-ui.theme.min.css delete mode 100644 ext/autocomplete/lib/jquery.tagit.css delete mode 100644 ext/autocomplete/lib/tag-it.min.js delete mode 100644 ext/autocomplete/lib/tagit.ui-zendesk.css delete mode 100644 ext/autocomplete/theme.php diff --git a/ext/autocomplete/lib/jquery-ui.min.css b/ext/autocomplete/lib/jquery-ui.min.css deleted file mode 100644 index 91f16a3b8d..0000000000 --- a/ext/autocomplete/lib/jquery-ui.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! jQuery UI - v1.11.2 - 2014-10-16 -* http://jqueryui.com -* Includes: core.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, draggable.css, menu.css, progressbar.css, resizable.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css -* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px -* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ - -.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;min-height:0;font-size:100%}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:none}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{position:relative;margin:0;padding:3px 1em 3px .4em;cursor:pointer;min-height:0;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-button{display:inline-block;overflow:hidden;position:relative;text-decoration:none;cursor:pointer}.ui-selectmenu-button span.ui-icon{right:0.5em;left:auto;margin-top:-8px;position:absolute;top:50%}.ui-selectmenu-button span.ui-selectmenu-text{text-align:left;padding:0.4em 2.1em 0.4em 1em;display:block;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #e78f08;background:#f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#1c94c4}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#1c94c4;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #fbcb09;background:#fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#c77405}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#c77405;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #fbd850;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#eb8f00}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#eb8f00;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fed22f;background:#ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_228ef1_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_ffd27a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);border-radius:5px} \ No newline at end of file diff --git a/ext/autocomplete/lib/jquery-ui.min.js b/ext/autocomplete/lib/jquery-ui.min.js deleted file mode 100644 index 17eab79039..0000000000 --- a/ext/autocomplete/lib/jquery-ui.min.js +++ /dev/null @@ -1,13 +0,0 @@ -/*! jQuery UI - v1.11.2 - 2014-10-16 -* http://jqueryui.com -* Includes: core.js, widget.js, mouse.js, position.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, draggable.js, droppable.js, effect.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js, menu.js, progressbar.js, resizable.js, selectable.js, selectmenu.js, slider.js, sortable.js, spinner.js, tabs.js, tooltip.js -* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ - -(function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(jQuery)})(function(e){function t(t,s){var n,a,o,r=t.nodeName.toLowerCase();return"area"===r?(n=t.parentNode,a=n.name,t.href&&a&&"map"===n.nodeName.toLowerCase()?(o=e("img[usemap='#"+a+"']")[0],!!o&&i(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||s:s)&&i(t)}function i(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}function s(e){for(var t,i;e.length&&e[0]!==document;){if(t=e.css("position"),("absolute"===t||"relative"===t||"fixed"===t)&&(i=parseInt(e.css("zIndex"),10),!isNaN(i)&&0!==i))return i;e=e.parent()}return 0}function n(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},e.extend(this._defaults,this.regional[""]),this.regional.en=e.extend(!0,{},this.regional[""]),this.regional["en-US"]=e.extend(!0,{},this.regional.en),this.dpDiv=a(e("
    "))}function a(t){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return t.delegate(i,"mouseout",function(){e(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).removeClass("ui-datepicker-next-hover")}).delegate(i,"mouseover",o)}function o(){e.datepicker._isDisabledDatepicker(v.inline?v.dpDiv.parent()[0]:v.input[0])||(e(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),e(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).addClass("ui-datepicker-next-hover"))}function r(t,i){e.extend(t,i);for(var s in i)null==i[s]&&(t[s]=i[s]);return t}function h(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.ui=e.ui||{},e.extend(e.ui,{version:"1.11.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({scrollParent:function(t){var i=this.css("position"),s="absolute"===i,n=t?/(auto|scroll|hidden)/:/(auto|scroll)/,a=this.parents().filter(function(){var t=e(this);return s&&"static"===t.css("position")?!1:n.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==i&&a.length?a:e(this[0].ownerDocument||document)},uniqueId:function(){var e=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++e)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,s){return!!e.data(t,s[3])},focusable:function(i){return t(i,!isNaN(e.attr(i,"tabindex")))},tabbable:function(i){var s=e.attr(i,"tabindex"),n=isNaN(s);return(n||s>=0)&&t(i,!n)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(t,i){function s(t,i,s,a){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,s&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),a&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],a=i.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+i]=function(t){return void 0===t?o["inner"+i].call(this):this.each(function(){e(this).css(a,s(this,t)+"px")})},e.fn["outer"+i]=function(t,n){return"number"!=typeof t?o["outer"+i].call(this,t):this.each(function(){e(this).css(a,s(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.fn.extend({focus:function(t){return function(i,s){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),s&&s.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),disableSelection:function(){var e="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.bind(e+".ui-disableSelection",function(e){e.preventDefault()})}}(),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(t){if(void 0!==t)return this.css("zIndex",t);if(this.length)for(var i,s,n=e(this[0]);n.length&&n[0]!==document;){if(i=n.css("position"),("absolute"===i||"relative"===i||"fixed"===i)&&(s=parseInt(n.css("zIndex"),10),!isNaN(s)&&0!==s))return s;n=n.parent()}return 0}}),e.ui.plugin={add:function(t,i,s){var n,a=e.ui[t].prototype;for(n in s)a.plugins[n]=a.plugins[n]||[],a.plugins[n].push([i,s[n]])},call:function(e,t,i,s){var n,a=e.plugins[t];if(a&&(s||e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType))for(n=0;a.length>n;n++)e.options[a[n][0]]&&a[n][1].apply(e.element,i)}};var l=0,u=Array.prototype.slice;e.cleanData=function(t){return function(i){var s,n,a;for(a=0;null!=(n=i[a]);a++)try{s=e._data(n,"events"),s&&s.remove&&e(n).triggerHandler("remove")}catch(o){}t(i)}}(e.cleanData),e.widget=function(t,i,s){var n,a,o,r,h={},l=t.split(".")[0];return t=t.split(".")[1],n=l+"-"+t,s||(s=i,i=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[l]=e[l]||{},a=e[l][t],o=e[l][t]=function(e,t){return this._createWidget?(arguments.length&&this._createWidget(e,t),void 0):new o(e,t)},e.extend(o,a,{version:s.version,_proto:e.extend({},s),_childConstructors:[]}),r=new i,r.options=e.widget.extend({},r.options),e.each(s,function(t,s){return e.isFunction(s)?(h[t]=function(){var e=function(){return i.prototype[t].apply(this,arguments)},n=function(e){return i.prototype[t].apply(this,e)};return function(){var t,i=this._super,a=this._superApply;return this._super=e,this._superApply=n,t=s.apply(this,arguments),this._super=i,this._superApply=a,t}}(),void 0):(h[t]=s,void 0)}),o.prototype=e.widget.extend(r,{widgetEventPrefix:a?r.widgetEventPrefix||t:t},h,{constructor:o,namespace:l,widgetName:t,widgetFullName:n}),a?(e.each(a._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete a._childConstructors):i._childConstructors.push(o),e.widget.bridge(t,o),o},e.widget.extend=function(t){for(var i,s,n=u.call(arguments,1),a=0,o=n.length;o>a;a++)for(i in n[a])s=n[a][i],n[a].hasOwnProperty(i)&&void 0!==s&&(t[i]=e.isPlainObject(s)?e.isPlainObject(t[i])?e.widget.extend({},t[i],s):e.widget.extend({},s):s);return t},e.widget.bridge=function(t,i){var s=i.prototype.widgetFullName||t;e.fn[t]=function(n){var a="string"==typeof n,o=u.call(arguments,1),r=this;return n=!a&&o.length?e.widget.extend.apply(null,[n].concat(o)):n,a?this.each(function(){var i,a=e.data(this,s);return"instance"===n?(r=a,!1):a?e.isFunction(a[n])&&"_"!==n.charAt(0)?(i=a[n].apply(a,o),i!==a&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):e.error("no such method '"+n+"' for "+t+" widget instance"):e.error("cannot call methods on "+t+" prior to initialization; "+"attempted to call method '"+n+"'")}):this.each(function(){var t=e.data(this,s);t?(t.option(n||{}),t._init&&t._init()):e.data(this,s,new i(n,this))}),r}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{disabled:!1,create:null},_createWidget:function(t,i){i=e(i||this.defaultElement||this)[0],this.element=e(i),this.uuid=l++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=e(),this.hoverable=e(),this.focusable=e(),i!==this&&(e.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===i&&this.destroy()}}),this.document=e(i.style?i.ownerDocument:i.document||i),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(t,i){var s,n,a,o=t;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof t)if(o={},s=t.split("."),t=s.shift(),s.length){for(n=o[t]=e.widget.extend({},this.options[t]),a=0;s.length-1>a;a++)n[s[a]]=n[s[a]]||{},n=n[s[a]];if(t=s.pop(),1===arguments.length)return void 0===n[t]?null:n[t];n[t]=i}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=i}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled",!!t),t&&(this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus"))),this},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_on:function(t,i,s){var n,a=this;"boolean"!=typeof t&&(s=i,i=t,t=!1),s?(i=n=e(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),e.each(s,function(s,o){function r(){return t||a.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?a[o]:o).apply(a,arguments):void 0}"string"!=typeof o&&(r.guid=o.guid=o.guid||r.guid||e.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+a.eventNamespace,u=h[2];u?n.delegate(u,l,r):i.bind(l,r)})},_off:function(t,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(i).undelegate(i),this.bindings=e(this.bindings.not(t).get()),this.focusable=e(this.focusable.not(t).get()),this.hoverable=e(this.hoverable.not(t).get())},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,o=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(o)&&o.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var o,r=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),o=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),o&&e.effects&&e.effects.effect[r]?s[t](n):r!==t&&s[r]?s[r](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}}),e.widget;var d=!1;e(document).mouseup(function(){d=!1}),e.widget("ui.mouse",{version:"1.11.2",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(!d){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var i=this,s=1===t.which,n="string"==typeof this.options.cancel&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(t)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(t)!==!1,!this._mouseStarted)?(t.preventDefault(),!0):(!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return i._mouseMove(e)},this._mouseUpDelegate=function(e){return i._mouseUp(e)},this.document.bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),d=!0,!0)):!0}},_mouseMove:function(t){if(this._mouseMoved){if(e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button)return this._mouseUp(t);if(!t.which)return this._mouseUp(t)}return(t.which||t.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),d=!1,!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),function(){function t(e,t,i){return[parseFloat(e[0])*(p.test(e[0])?t/100:1),parseFloat(e[1])*(p.test(e[1])?i/100:1)]}function i(t,i){return parseInt(e.css(t,i),10)||0}function s(t){var i=t[0];return 9===i.nodeType?{width:t.width(),height:t.height(),offset:{top:0,left:0}}:e.isWindow(i)?{width:t.width(),height:t.height(),offset:{top:t.scrollTop(),left:t.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:t.outerWidth(),height:t.outerHeight(),offset:t.offset()}}e.ui=e.ui||{};var n,a,o=Math.max,r=Math.abs,h=Math.round,l=/left|center|right/,u=/top|center|bottom/,d=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,p=/%$/,f=e.fn.position;e.position={scrollbarWidth:function(){if(void 0!==n)return n;var t,i,s=e("
    "),a=s.children()[0];return e("body").append(s),t=a.offsetWidth,s.css("overflow","scroll"),i=a.offsetWidth,t===i&&(i=s[0].clientWidth),s.remove(),n=t-i},getScrollInfo:function(t){var i=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),s=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),n="scroll"===i||"auto"===i&&t.widthi?"left":t>0?"right":"center",vertical:0>a?"top":s>0?"bottom":"middle"};d>m&&m>r(t+i)&&(h.horizontal="center"),c>g&&g>r(s+a)&&(h.vertical="middle"),h.important=o(r(t),r(i))>o(r(s),r(a))?"horizontal":"vertical",n.using.call(this,e,h)}),u.offset(e.extend(M,{using:l}))})},e.ui.position={fit:{left:function(e,t){var i,s=t.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=e.left-t.collisionPosition.marginLeft,h=n-r,l=r+t.collisionWidth-a-n;t.collisionWidth>a?h>0&&0>=l?(i=e.left+h+t.collisionWidth-a-n,e.left+=h-i):e.left=l>0&&0>=h?n:h>l?n+a-t.collisionWidth:n:h>0?e.left+=h:l>0?e.left-=l:e.left=o(e.left-r,e.left)},top:function(e,t){var i,s=t.within,n=s.isWindow?s.scrollTop:s.offset.top,a=t.within.height,r=e.top-t.collisionPosition.marginTop,h=n-r,l=r+t.collisionHeight-a-n;t.collisionHeight>a?h>0&&0>=l?(i=e.top+h+t.collisionHeight-a-n,e.top+=h-i):e.top=l>0&&0>=h?n:h>l?n+a-t.collisionHeight:n:h>0?e.top+=h:l>0?e.top-=l:e.top=o(e.top-r,e.top)}},flip:{left:function(e,t){var i,s,n=t.within,a=n.offset.left+n.scrollLeft,o=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=e.left-t.collisionPosition.marginLeft,u=l-h,d=l+t.collisionWidth-o-h,c="left"===t.my[0]?-t.elemWidth:"right"===t.my[0]?t.elemWidth:0,p="left"===t.at[0]?t.targetWidth:"right"===t.at[0]?-t.targetWidth:0,f=-2*t.offset[0];0>u?(i=e.left+c+p+f+t.collisionWidth-o-a,(0>i||r(u)>i)&&(e.left+=c+p+f)):d>0&&(s=e.left-t.collisionPosition.marginLeft+c+p+f-h,(s>0||d>r(s))&&(e.left+=c+p+f))},top:function(e,t){var i,s,n=t.within,a=n.offset.top+n.scrollTop,o=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=e.top-t.collisionPosition.marginTop,u=l-h,d=l+t.collisionHeight-o-h,c="top"===t.my[1],p=c?-t.elemHeight:"bottom"===t.my[1]?t.elemHeight:0,f="top"===t.at[1]?t.targetHeight:"bottom"===t.at[1]?-t.targetHeight:0,m=-2*t.offset[1];0>u?(s=e.top+p+f+m+t.collisionHeight-o-a,e.top+p+f+m>u&&(0>s||r(u)>s)&&(e.top+=p+f+m)):d>0&&(i=e.top-t.collisionPosition.marginTop+p+f+m-h,e.top+p+f+m>d&&(i>0||d>r(i))&&(e.top+=p+f+m))}},flipfit:{left:function(){e.ui.position.flip.left.apply(this,arguments),e.ui.position.fit.left.apply(this,arguments)},top:function(){e.ui.position.flip.top.apply(this,arguments),e.ui.position.fit.top.apply(this,arguments)}}},function(){var t,i,s,n,o,r=document.getElementsByTagName("body")[0],h=document.createElement("div");t=document.createElement(r?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},r&&e.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(o in s)t.style[o]=s[o];t.appendChild(h),i=r||document.documentElement,i.insertBefore(t,i.firstChild),h.style.cssText="position: absolute; left: 10.7432222px;",n=e(h).offset().left,a=n>10&&11>n,t.innerHTML="",i.removeChild(t)}()}(),e.ui.position,e.widget("ui.accordion",{version:"1.11.2",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var t=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),t.collapsible||t.active!==!1&&null!=t.active||(t.active=0),this._processPanels(),0>t.active&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").removeUniqueId(),this._destroyIcons(),e=this.headers.next().removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").css("display","").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&e.css("height","")},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):("event"===e&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),"collapsible"!==e||t||this.options.active!==!1||this._activate(0),"icons"===e&&(this._destroyIcons(),t&&this._createIcons()),"disabled"===e&&(this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)),void 0)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var i=e.ui.keyCode,s=this.headers.length,n=this.headers.index(t.target),a=!1;switch(t.keyCode){case i.RIGHT:case i.DOWN:a=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:a=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(t);break;case i.HOME:a=this.headers[0];break;case i.END:a=this.headers[s-1]}a&&(e(t.target).attr("tabIndex",-1),e(a).attr("tabIndex",0),a.focus(),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t=this.options;this._processPanels(),t.active===!1&&t.collapsible===!0||!this.headers.length?(t.active=!1,this.active=e()):t.active===!1?this._activate(0):this.active.length&&!e.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=e()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var e=this.headers,t=this.panels;this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-state-default ui-corner-all"),this.panels=this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide(),t&&(this._off(e.not(this.headers)),this._off(t.not(this.panels)))},_refresh:function(){var t,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(){var t=e(this),i=t.uniqueId().attr("id"),s=t.next(),n=s.uniqueId().attr("id");t.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(t=n.height(),this.element.siblings(":visible").each(function(){var i=e(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(t-=i.outerHeight(!0))}),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===s&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var i=this._findActive(t)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):e()},_setupEvents:function(t){var i={keydown:"_keydown"};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n[0]===s[0],o=a&&i.collapsible,r=o?e():n.next(),h=s.next(),l={oldHeader:s,oldPanel:h,newHeader:o?e():n,newPanel:r};t.preventDefault(),a&&!i.collapsible||this._trigger("beforeActivate",t,l)===!1||(i.active=o?!1:this.headers.index(n),this.active=a?e():n,this._toggle(l),s.removeClass("ui-accordion-header-active ui-state-active"),i.icons&&s.children(".ui-accordion-header-icon").removeClass(i.icons.activeHeader).addClass(i.icons.header),a||(n.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),i.icons&&n.children(".ui-accordion-header-icon").removeClass(i.icons.header).addClass(i.icons.activeHeader),n.next().addClass("ui-accordion-content-active")))},_toggle:function(t){var i=t.newPanel,s=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,t):(s.hide(),i.show(),this._toggleComplete(t)),s.attr({"aria-hidden":"true"}),s.prev().attr("aria-selected","false"),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true",tabIndex:0,"aria-expanded":"true"})},_animate:function(e,t,i){var s,n,a,o=this,r=0,h=e.length&&(!t.length||e.index()",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},items:"> *",menus:"ul",position:{my:"left-1 top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item":function(e){e.preventDefault()},"click .ui-menu-item":function(t){var i=e(t.target);!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(t),t.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(t):!this.element.is(":focus")&&e(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){if(!this.previousFilter){var i=e(t.currentTarget);i.siblings(".ui-state-active").removeClass("ui-state-active"),this.focus(t,i) -}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var i=this.active||this.element.find(this.options.items).eq(0);t||this.focus(e,i)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){this._closeOnDocumentClick(e)&&this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-menu-icons ui-front").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").removeUniqueId().removeClass("ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){var i,s,n,a,o=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:o=!1,s=this.previousFilter||"",n=String.fromCharCode(t.keyCode),a=!1,clearTimeout(this.filterTimer),n===s?a=!0:n=s+n,i=this._filterMenuItems(n),i=a&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(t.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(t,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}o&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.is("[aria-haspopup='true']")?this.expand(e):this.select(e))},refresh:function(){var t,i,s=this,n=this.options.icons.submenu,a=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),a.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-front").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),i=t.parent(),s=e("").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);i.attr("aria-haspopup","true").prepend(s),t.attr("aria-labelledby",i.attr("id"))}),t=a.add(this.element),i=t.find(this.options.items),i.not(".ui-menu-item").each(function(){var t=e(this);s._isDivider(t)&&t.addClass("ui-widget-content ui-menu-divider")}),i.not(".ui-menu-item, .ui-menu-divider").addClass("ui-menu-item").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(e,t){"icons"===e&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(t.submenu),"disabled"===e&&this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this._super(e,t)},focus:function(e,t){var i,s;this.blur(e,e&&"focus"===e.type),this._scrollIntoView(t),this.active=t.first(),s=this.active.addClass("ui-state-focus").removeClass("ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").addClass("ui-state-active"),e&&"keydown"===e.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=t.children(".ui-menu"),i.length&&e&&/^mouse/.test(e.type)&&this._startOpening(i),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,n=t.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=t.outerHeight(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(e,t){t||clearTimeout(this.timer),this.active&&(this.active.removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active}))},_startOpening:function(e){clearTimeout(this.timer),"true"===e.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(e)},this.delay))},_open:function(t){var i=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(t,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(t),this.activeMenu=s},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find(".ui-state-active").not(".ui-state-focus").removeClass("ui-state-active")},_closeOnDocumentClick:function(t){return!e(t.target).closest(".ui-menu").length},_isDivider:function(e){return!/[^\-\u2014\u2013\s]/.test(e.text())},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,i){var s;this.active&&(s="first"===e||"last"===e?this.active["first"===e?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[e+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[t]()),this.focus(i,s)},nextPage:function(t){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=e(this),0>i.offset().top-s-n}),this.focus(t,i)):this.focus(t,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(t),void 0)},previousPage:function(t){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=e(this),i.offset().top-s+n>0}),this.focus(t,i)):this.focus(t,this.activeMenu.find(this.options.items).first())),void 0):(this.next(t),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,void 0;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),void 0):(this._searchTimeout(e),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(e),this._change(e),void 0)}}),this._initSource(),this.menu=e("
      ").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var i=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(s){s.target===t.element[0]||s.target===i||e.contains(i,s.target)||t.close()})})},menufocus:function(t,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",t,{item:n})&&t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&e.trim(s).length&&(this.liveRegion.children().hide(),e("
      ").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,t){var i=t.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",e,{item:i})&&this._value(i.value),this.term=this._value(),this.close(e),this.selectedItem=i}}),this.liveRegion=e("",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).addClass("ui-helper-hidden-accessible").appendTo(this.document[0].body),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),"source"===e&&this._initSource(),"appendTo"===e&&this.menu.element.appendTo(this._appendTo()),"disabled"===e&&t&&this.xhr&&this.xhr.abort()},_appendTo:function(){var t=this.options.appendTo;return t&&(t=t.jquery||t.nodeType?e(t):this.document.find(t).eq(0)),t&&t[0]||(t=this.element.closest(".ui-front")),t.length||(t=this.document[0].body),t},_initSource:function(){var t,i,s=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(i,s){s(e.ui.autocomplete.filter(t,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(t,n){s.xhr&&s.xhr.abort(),s.xhr=e.ajax({url:i,data:t,dataType:"json",success:function(e){n(e)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){var t=this.term===this._value(),i=this.menu.element.is(":visible"),s=e.altKey||e.ctrlKey||e.metaKey||e.shiftKey;(!t||t&&!i&&!s)&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){return e=null!=e?e:this._value(),this.term=this._value(),e.length").text(i.label).appendTo(t)},_move:function(e,t){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[e](t),void 0):(this.search(null,t),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(e,t),t.preventDefault())}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,i){var s=RegExp(e.ui.autocomplete.escapeRegex(i),"i");return e.grep(t,function(e){return s.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(t){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=t&&t.length?this.options.messages.results(t.length):this.options.messages.noResults,this.liveRegion.children().hide(),e("
      ").text(i).appendTo(this.liveRegion))}}),e.ui.autocomplete;var c,p="ui-button ui-widget ui-state-default ui-corner-all",f="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",m=function(){var t=e(this);setTimeout(function(){t.find(":ui-button").button("refresh")},1)},g=function(t){var i=t.name,s=t.form,n=e([]);return i&&(i=i.replace(/'/g,"\\'"),n=s?e(s).find("[name='"+i+"'][type=radio]"):e("[name='"+i+"'][type=radio]",t.ownerDocument).filter(function(){return!this.form})),n};e.widget("ui.button",{version:"1.11.2",defaultElement:"").addClass(this._triggerClass).html(a?e("").attr({src:a,alt:n,title:n}):n)),t[r?"before":"after"](i.trigger),i.trigger.click(function(){return e.datepicker._datepickerShowing&&e.datepicker._lastInput===t[0]?e.datepicker._hideDatepicker():e.datepicker._datepickerShowing&&e.datepicker._lastInput!==t[0]?(e.datepicker._hideDatepicker(),e.datepicker._showDatepicker(t[0])):e.datepicker._showDatepicker(t[0]),!1}))},_autoSize:function(e){if(this._get(e,"autoSize")&&!e.inline){var t,i,s,n,a=new Date(2009,11,20),o=this._get(e,"dateFormat");o.match(/[DM]/)&&(t=function(e){for(i=0,s=0,n=0;e.length>n;n++)e[n].length>i&&(i=e[n].length,s=n);return s},a.setMonth(t(this._get(e,o.match(/MM/)?"monthNames":"monthNamesShort"))),a.setDate(t(this._get(e,o.match(/DD/)?"dayNames":"dayNamesShort"))+20-a.getDay())),e.input.attr("size",this._formatDate(e,a).length)}},_inlineDatepicker:function(t,i){var s=e(t);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),e.data(t,"datepicker",i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(t),i.dpDiv.css("display","block"))},_dialogDatepicker:function(t,i,s,n,a){var o,h,l,u,d,c=this._dialogInst;return c||(this.uuid+=1,o="dp"+this.uuid,this._dialogInput=e(""),this._dialogInput.keydown(this._doKeyDown),e("body").append(this._dialogInput),c=this._dialogInst=this._newInst(this._dialogInput,!1),c.settings={},e.data(this._dialogInput[0],"datepicker",c)),r(c.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(c,i):i,this._dialogInput.val(i),this._pos=a?a.length?a:[a.pageX,a.pageY]:null,this._pos||(h=document.documentElement.clientWidth,l=document.documentElement.clientHeight,u=document.documentElement.scrollLeft||document.body.scrollLeft,d=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[h/2-100+u,l/2-150+d]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),c.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),e.blockUI&&e.blockUI(this.dpDiv),e.data(this._dialogInput[0],"datepicker",c),this},_destroyDatepicker:function(t){var i,s=e(t),n=e.data(t,"datepicker");s.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),e.removeData(t,"datepicker"),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty())},_enableDatepicker:function(t){var i,s,n=e(t),a=e.data(t,"datepicker");n.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!1,a.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}))},_disableDatepicker:function(t){var i,s,n=e(t),a=e.data(t,"datepicker");n.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!0,a.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}),this._disabledInputs[this._disabledInputs.length]=t)},_isDisabledDatepicker:function(e){if(!e)return!1;for(var t=0;this._disabledInputs.length>t;t++)if(this._disabledInputs[t]===e)return!0;return!1},_getInst:function(t){try{return e.data(t,"datepicker")}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(t,i,s){var n,a,o,h,l=this._getInst(t);return 2===arguments.length&&"string"==typeof i?"defaults"===i?e.extend({},e.datepicker._defaults):l?"all"===i?e.extend({},l.settings):this._get(l,i):null:(n=i||{},"string"==typeof i&&(n={},n[i]=s),l&&(this._curInst===l&&this._hideDatepicker(),a=this._getDateDatepicker(t,!0),o=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),r(l.settings,n),null!==o&&void 0!==n.dateFormat&&void 0===n.minDate&&(l.settings.minDate=this._formatDate(l,o)),null!==h&&void 0!==n.dateFormat&&void 0===n.maxDate&&(l.settings.maxDate=this._formatDate(l,h)),"disabled"in n&&(n.disabled?this._disableDatepicker(t):this._enableDatepicker(t)),this._attachments(e(t),l),this._autoSize(l),this._setDate(l,a),this._updateAlternate(l),this._updateDatepicker(l)),void 0)},_changeDatepicker:function(e,t,i){this._optionDatepicker(e,t,i)},_refreshDatepicker:function(e){var t=this._getInst(e);t&&this._updateDatepicker(t)},_setDateDatepicker:function(e,t){var i=this._getInst(e);i&&(this._setDate(i,t),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(e,t){var i=this._getInst(e);return i&&!i.inline&&this._setDateFromField(i,t),i?this._getDate(i):null},_doKeyDown:function(t){var i,s,n,a=e.datepicker._getInst(t.target),o=!0,r=a.dpDiv.is(".ui-datepicker-rtl");if(a._keyEvent=!0,e.datepicker._datepickerShowing)switch(t.keyCode){case 9:e.datepicker._hideDatepicker(),o=!1;break;case 13:return n=e("td."+e.datepicker._dayOverClass+":not(."+e.datepicker._currentClass+")",a.dpDiv),n[0]&&e.datepicker._selectDay(t.target,a.selectedMonth,a.selectedYear,n[0]),i=e.datepicker._get(a,"onSelect"),i?(s=e.datepicker._formatDate(a),i.apply(a.input?a.input[0]:null,[s,a])):e.datepicker._hideDatepicker(),!1;case 27:e.datepicker._hideDatepicker();break;case 33:e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(a,"stepBigMonths"):-e.datepicker._get(a,"stepMonths"),"M");break;case 34:e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(a,"stepBigMonths"):+e.datepicker._get(a,"stepMonths"),"M");break;case 35:(t.ctrlKey||t.metaKey)&&e.datepicker._clearDate(t.target),o=t.ctrlKey||t.metaKey;break;case 36:(t.ctrlKey||t.metaKey)&&e.datepicker._gotoToday(t.target),o=t.ctrlKey||t.metaKey;break;case 37:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,r?1:-1,"D"),o=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(a,"stepBigMonths"):-e.datepicker._get(a,"stepMonths"),"M");break;case 38:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,-7,"D"),o=t.ctrlKey||t.metaKey;break;case 39:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,r?-1:1,"D"),o=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(a,"stepBigMonths"):+e.datepicker._get(a,"stepMonths"),"M");break;case 40:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,7,"D"),o=t.ctrlKey||t.metaKey;break;default:o=!1}else 36===t.keyCode&&t.ctrlKey?e.datepicker._showDatepicker(this):o=!1;o&&(t.preventDefault(),t.stopPropagation())},_doKeyPress:function(t){var i,s,n=e.datepicker._getInst(t.target);return e.datepicker._get(n,"constrainInput")?(i=e.datepicker._possibleChars(e.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==t.charCode?t.keyCode:t.charCode),t.ctrlKey||t.metaKey||" ">s||!i||i.indexOf(s)>-1):void 0 -},_doKeyUp:function(t){var i,s=e.datepicker._getInst(t.target);if(s.input.val()!==s.lastVal)try{i=e.datepicker.parseDate(e.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,e.datepicker._getFormatConfig(s)),i&&(e.datepicker._setDateFromField(s),e.datepicker._updateAlternate(s),e.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(t){if(t=t.target||t,"input"!==t.nodeName.toLowerCase()&&(t=e("input",t.parentNode)[0]),!e.datepicker._isDisabledDatepicker(t)&&e.datepicker._lastInput!==t){var i,n,a,o,h,l,u;i=e.datepicker._getInst(t),e.datepicker._curInst&&e.datepicker._curInst!==i&&(e.datepicker._curInst.dpDiv.stop(!0,!0),i&&e.datepicker._datepickerShowing&&e.datepicker._hideDatepicker(e.datepicker._curInst.input[0])),n=e.datepicker._get(i,"beforeShow"),a=n?n.apply(t,[t,i]):{},a!==!1&&(r(i.settings,a),i.lastVal=null,e.datepicker._lastInput=t,e.datepicker._setDateFromField(i),e.datepicker._inDialog&&(t.value=""),e.datepicker._pos||(e.datepicker._pos=e.datepicker._findPos(t),e.datepicker._pos[1]+=t.offsetHeight),o=!1,e(t).parents().each(function(){return o|="fixed"===e(this).css("position"),!o}),h={left:e.datepicker._pos[0],top:e.datepicker._pos[1]},e.datepicker._pos=null,i.dpDiv.empty(),i.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),e.datepicker._updateDatepicker(i),h=e.datepicker._checkOffset(i,h,o),i.dpDiv.css({position:e.datepicker._inDialog&&e.blockUI?"static":o?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"}),i.inline||(l=e.datepicker._get(i,"showAnim"),u=e.datepicker._get(i,"duration"),i.dpDiv.css("z-index",s(e(t))+1),e.datepicker._datepickerShowing=!0,e.effects&&e.effects.effect[l]?i.dpDiv.show(l,e.datepicker._get(i,"showOptions"),u):i.dpDiv[l||"show"](l?u:null),e.datepicker._shouldFocusInput(i)&&i.input.focus(),e.datepicker._curInst=i))}},_updateDatepicker:function(t){this.maxRows=4,v=t,t.dpDiv.empty().append(this._generateHTML(t)),this._attachHandlers(t);var i,s=this._getNumberOfMonths(t),n=s[1],a=17,r=t.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&o.apply(r.get(0)),t.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&t.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),t.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),t.dpDiv[(this._get(t,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),t===e.datepicker._curInst&&e.datepicker._datepickerShowing&&e.datepicker._shouldFocusInput(t)&&t.input.focus(),t.yearshtml&&(i=t.yearshtml,setTimeout(function(){i===t.yearshtml&&t.yearshtml&&t.dpDiv.find("select.ui-datepicker-year:first").replaceWith(t.yearshtml),i=t.yearshtml=null},0))},_shouldFocusInput:function(e){return e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&!e.input.is(":focus")},_checkOffset:function(t,i,s){var n=t.dpDiv.outerWidth(),a=t.dpDiv.outerHeight(),o=t.input?t.input.outerWidth():0,r=t.input?t.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:e(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:e(document).scrollTop());return i.left-=this._get(t,"isRTL")?n-o:0,i.left-=s&&i.left===t.input.offset().left?e(document).scrollLeft():0,i.top-=s&&i.top===t.input.offset().top+r?e(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+a>l&&l>a?Math.abs(a+r):0),i},_findPos:function(t){for(var i,s=this._getInst(t),n=this._get(s,"isRTL");t&&("hidden"===t.type||1!==t.nodeType||e.expr.filters.hidden(t));)t=t[n?"previousSibling":"nextSibling"];return i=e(t).offset(),[i.left,i.top]},_hideDatepicker:function(t){var i,s,n,a,o=this._curInst;!o||t&&o!==e.data(t,"datepicker")||this._datepickerShowing&&(i=this._get(o,"showAnim"),s=this._get(o,"duration"),n=function(){e.datepicker._tidyDialog(o)},e.effects&&(e.effects.effect[i]||e.effects[i])?o.dpDiv.hide(i,e.datepicker._get(o,"showOptions"),s,n):o.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,a=this._get(o,"onClose"),a&&a.apply(o.input?o.input[0]:null,[o.input?o.input.val():"",o]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),e.blockUI&&(e.unblockUI(),e("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(e){e.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(t){if(e.datepicker._curInst){var i=e(t.target),s=e.datepicker._getInst(i[0]);(i[0].id!==e.datepicker._mainDivId&&0===i.parents("#"+e.datepicker._mainDivId).length&&!i.hasClass(e.datepicker.markerClassName)&&!i.closest("."+e.datepicker._triggerClass).length&&e.datepicker._datepickerShowing&&(!e.datepicker._inDialog||!e.blockUI)||i.hasClass(e.datepicker.markerClassName)&&e.datepicker._curInst!==s)&&e.datepicker._hideDatepicker()}},_adjustDate:function(t,i,s){var n=e(t),a=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(a,i+("M"===s?this._get(a,"showCurrentAtPos"):0),s),this._updateDatepicker(a))},_gotoToday:function(t){var i,s=e(t),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(t,i,s){var n=e(t),a=this._getInst(n[0]);a["selected"+("M"===s?"Month":"Year")]=a["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(a),this._adjustDate(n)},_selectDay:function(t,i,s,n){var a,o=e(t);e(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(o[0])||(a=this._getInst(o[0]),a.selectedDay=a.currentDay=e("a",n).html(),a.selectedMonth=a.currentMonth=i,a.selectedYear=a.currentYear=s,this._selectDate(t,this._formatDate(a,a.currentDay,a.currentMonth,a.currentYear)))},_clearDate:function(t){var i=e(t);this._selectDate(i,"")},_selectDate:function(t,i){var s,n=e(t),a=this._getInst(n[0]);i=null!=i?i:this._formatDate(a),a.input&&a.input.val(i),this._updateAlternate(a),s=this._get(a,"onSelect"),s?s.apply(a.input?a.input[0]:null,[i,a]):a.input&&a.input.trigger("change"),a.inline?this._updateDatepicker(a):(this._hideDatepicker(),this._lastInput=a.input[0],"object"!=typeof a.input[0]&&a.input.focus(),this._lastInput=null)},_updateAlternate:function(t){var i,s,n,a=this._get(t,"altField");a&&(i=this._get(t,"altFormat")||this._get(t,"dateFormat"),s=this._getDate(t),n=this.formatDate(i,s,this._getFormatConfig(t)),e(a).each(function(){e(this).val(n)}))},noWeekends:function(e){var t=e.getDay();return[t>0&&6>t,""]},iso8601Week:function(e){var t,i=new Date(e.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),t=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((t-i)/864e5)/7)+1},parseDate:function(t,i,s){if(null==t||null==i)throw"Invalid arguments";if(i="object"==typeof i?""+i:i+"",""===i)return null;var n,a,o,r,h=0,l=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,u="string"!=typeof l?l:(new Date).getFullYear()%100+parseInt(l,10),d=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,c=(s?s.dayNames:null)||this._defaults.dayNames,p=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,f=(s?s.monthNames:null)||this._defaults.monthNames,m=-1,g=-1,v=-1,y=-1,b=!1,_=function(e){var i=t.length>n+1&&t.charAt(n+1)===e;return i&&n++,i},x=function(e){var t=_(e),s="@"===e?14:"!"===e?20:"y"===e&&t?4:"o"===e?3:2,n="y"===e?s:1,a=RegExp("^\\d{"+n+","+s+"}"),o=i.substring(h).match(a);if(!o)throw"Missing number at position "+h;return h+=o[0].length,parseInt(o[0],10)},w=function(t,s,n){var a=-1,o=e.map(_(t)?n:s,function(e,t){return[[t,e]]}).sort(function(e,t){return-(e[1].length-t[1].length)});if(e.each(o,function(e,t){var s=t[1];return i.substr(h,s.length).toLowerCase()===s.toLowerCase()?(a=t[0],h+=s.length,!1):void 0}),-1!==a)return a+1;throw"Unknown name at position "+h},k=function(){if(i.charAt(h)!==t.charAt(n))throw"Unexpected literal at position "+h;h++};for(n=0;t.length>n;n++)if(b)"'"!==t.charAt(n)||_("'")?k():b=!1;else switch(t.charAt(n)){case"d":v=x("d");break;case"D":w("D",d,c);break;case"o":y=x("o");break;case"m":g=x("m");break;case"M":g=w("M",p,f);break;case"y":m=x("y");break;case"@":r=new Date(x("@")),m=r.getFullYear(),g=r.getMonth()+1,v=r.getDate();break;case"!":r=new Date((x("!")-this._ticksTo1970)/1e4),m=r.getFullYear(),g=r.getMonth()+1,v=r.getDate();break;case"'":_("'")?k():b=!0;break;default:k()}if(i.length>h&&(o=i.substr(h),!/^\s+/.test(o)))throw"Extra/unparsed characters found in date: "+o;if(-1===m?m=(new Date).getFullYear():100>m&&(m+=(new Date).getFullYear()-(new Date).getFullYear()%100+(u>=m?0:-100)),y>-1)for(g=1,v=y;;){if(a=this._getDaysInMonth(m,g-1),a>=v)break;g++,v-=a}if(r=this._daylightSavingAdjust(new Date(m,g-1,v)),r.getFullYear()!==m||r.getMonth()+1!==g||r.getDate()!==v)throw"Invalid date";return r},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(e,t,i){if(!t)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,a=(i?i.dayNames:null)||this._defaults.dayNames,o=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(t){var i=e.length>s+1&&e.charAt(s+1)===t;return i&&s++,i},l=function(e,t,i){var s=""+t;if(h(e))for(;i>s.length;)s="0"+s;return s},u=function(e,t,i,s){return h(e)?s[t]:i[t]},d="",c=!1;if(t)for(s=0;e.length>s;s++)if(c)"'"!==e.charAt(s)||h("'")?d+=e.charAt(s):c=!1;else switch(e.charAt(s)){case"d":d+=l("d",t.getDate(),2);break;case"D":d+=u("D",t.getDay(),n,a);break;case"o":d+=l("o",Math.round((new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()-new Date(t.getFullYear(),0,0).getTime())/864e5),3);break;case"m":d+=l("m",t.getMonth()+1,2);break;case"M":d+=u("M",t.getMonth(),o,r);break;case"y":d+=h("y")?t.getFullYear():(10>t.getYear()%100?"0":"")+t.getYear()%100;break;case"@":d+=t.getTime();break;case"!":d+=1e4*t.getTime()+this._ticksTo1970;break;case"'":h("'")?d+="'":c=!0;break;default:d+=e.charAt(s)}return d},_possibleChars:function(e){var t,i="",s=!1,n=function(i){var s=e.length>t+1&&e.charAt(t+1)===i;return s&&t++,s};for(t=0;e.length>t;t++)if(s)"'"!==e.charAt(t)||n("'")?i+=e.charAt(t):s=!1;else switch(e.charAt(t)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=e.charAt(t)}return i},_get:function(e,t){return void 0!==e.settings[t]?e.settings[t]:this._defaults[t]},_setDateFromField:function(e,t){if(e.input.val()!==e.lastVal){var i=this._get(e,"dateFormat"),s=e.lastVal=e.input?e.input.val():null,n=this._getDefaultDate(e),a=n,o=this._getFormatConfig(e);try{a=this.parseDate(i,s,o)||n}catch(r){s=t?"":s}e.selectedDay=a.getDate(),e.drawMonth=e.selectedMonth=a.getMonth(),e.drawYear=e.selectedYear=a.getFullYear(),e.currentDay=s?a.getDate():0,e.currentMonth=s?a.getMonth():0,e.currentYear=s?a.getFullYear():0,this._adjustInstDate(e)}},_getDefaultDate:function(e){return this._restrictMinMax(e,this._determineDate(e,this._get(e,"defaultDate"),new Date))},_determineDate:function(t,i,s){var n=function(e){var t=new Date;return t.setDate(t.getDate()+e),t},a=function(i){try{return e.datepicker.parseDate(e.datepicker._get(t,"dateFormat"),i,e.datepicker._getFormatConfig(t))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?e.datepicker._getDate(t):null)||new Date,a=n.getFullYear(),o=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":o+=parseInt(l[1],10),r=Math.min(r,e.datepicker._getDaysInMonth(a,o));break;case"y":case"Y":a+=parseInt(l[1],10),r=Math.min(r,e.datepicker._getDaysInMonth(a,o))}l=h.exec(i)}return new Date(a,o,r)},o=null==i||""===i?s:"string"==typeof i?a(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return o=o&&"Invalid Date"==""+o?s:o,o&&(o.setHours(0),o.setMinutes(0),o.setSeconds(0),o.setMilliseconds(0)),this._daylightSavingAdjust(o)},_daylightSavingAdjust:function(e){return e?(e.setHours(e.getHours()>12?e.getHours()+2:0),e):null},_setDate:function(e,t,i){var s=!t,n=e.selectedMonth,a=e.selectedYear,o=this._restrictMinMax(e,this._determineDate(e,t,new Date));e.selectedDay=e.currentDay=o.getDate(),e.drawMonth=e.selectedMonth=e.currentMonth=o.getMonth(),e.drawYear=e.selectedYear=e.currentYear=o.getFullYear(),n===e.selectedMonth&&a===e.selectedYear||i||this._notifyChange(e),this._adjustInstDate(e),e.input&&e.input.val(s?"":this._formatDate(e))},_getDate:function(e){var t=!e.currentYear||e.input&&""===e.input.val()?null:this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return t},_attachHandlers:function(t){var i=this._get(t,"stepMonths"),s="#"+t.id.replace(/\\\\/g,"\\");t.dpDiv.find("[data-handler]").map(function(){var t={prev:function(){e.datepicker._adjustDate(s,-i,"M")},next:function(){e.datepicker._adjustDate(s,+i,"M")},hide:function(){e.datepicker._hideDatepicker()},today:function(){e.datepicker._gotoToday(s)},selectDay:function(){return e.datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return e.datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return e.datepicker._selectMonthYear(s,this,"Y"),!1}};e(this).bind(this.getAttribute("data-event"),t[this.getAttribute("data-handler")])})},_generateHTML:function(e){var t,i,s,n,a,o,r,h,l,u,d,c,p,f,m,g,v,y,b,_,x,w,k,T,D,S,M,C,N,A,P,I,z,H,F,E,O,j,W,L=new Date,R=this._daylightSavingAdjust(new Date(L.getFullYear(),L.getMonth(),L.getDate())),Y=this._get(e,"isRTL"),B=this._get(e,"showButtonPanel"),J=this._get(e,"hideIfNoPrevNext"),q=this._get(e,"navigationAsDateFormat"),K=this._getNumberOfMonths(e),V=this._get(e,"showCurrentAtPos"),U=this._get(e,"stepMonths"),Q=1!==K[0]||1!==K[1],G=this._daylightSavingAdjust(e.currentDay?new Date(e.currentYear,e.currentMonth,e.currentDay):new Date(9999,9,9)),X=this._getMinMaxDate(e,"min"),$=this._getMinMaxDate(e,"max"),Z=e.drawMonth-V,et=e.drawYear;if(0>Z&&(Z+=12,et--),$)for(t=this._daylightSavingAdjust(new Date($.getFullYear(),$.getMonth()-K[0]*K[1]+1,$.getDate())),t=X&&X>t?X:t;this._daylightSavingAdjust(new Date(et,Z,1))>t;)Z--,0>Z&&(Z=11,et--);for(e.drawMonth=Z,e.drawYear=et,i=this._get(e,"prevText"),i=q?this.formatDate(i,this._daylightSavingAdjust(new Date(et,Z-U,1)),this._getFormatConfig(e)):i,s=this._canAdjustMonth(e,-1,et,Z)?""+i+"":J?"":""+i+"",n=this._get(e,"nextText"),n=q?this.formatDate(n,this._daylightSavingAdjust(new Date(et,Z+U,1)),this._getFormatConfig(e)):n,a=this._canAdjustMonth(e,1,et,Z)?""+n+"":J?"":""+n+"",o=this._get(e,"currentText"),r=this._get(e,"gotoCurrent")&&e.currentDay?G:R,o=q?this.formatDate(o,r,this._getFormatConfig(e)):o,h=e.inline?"":"",l=B?"
      "+(Y?h:"")+(this._isInRange(e,r)?"":"")+(Y?"":h)+"
      ":"",u=parseInt(this._get(e,"firstDay"),10),u=isNaN(u)?0:u,d=this._get(e,"showWeek"),c=this._get(e,"dayNames"),p=this._get(e,"dayNamesMin"),f=this._get(e,"monthNames"),m=this._get(e,"monthNamesShort"),g=this._get(e,"beforeShowDay"),v=this._get(e,"showOtherMonths"),y=this._get(e,"selectOtherMonths"),b=this._getDefaultDate(e),_="",w=0;K[0]>w;w++){for(k="",this.maxRows=4,T=0;K[1]>T;T++){if(D=this._daylightSavingAdjust(new Date(et,Z,e.selectedDay)),S=" ui-corner-all",M="",Q){if(M+="
      "}for(M+="
      "+(/all|left/.test(S)&&0===w?Y?a:s:"")+(/all|right/.test(S)&&0===w?Y?s:a:"")+this._generateMonthYearHeader(e,Z,et,X,$,w>0||T>0,f,m)+"
      "+"",C=d?"":"",x=0;7>x;x++)N=(x+u)%7,C+="";for(M+=C+"",A=this._getDaysInMonth(et,Z),et===e.selectedYear&&Z===e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,A)),P=(this._getFirstDayOfMonth(et,Z)-u+7)%7,I=Math.ceil((P+A)/7),z=Q?this.maxRows>I?this.maxRows:I:I,this.maxRows=z,H=this._daylightSavingAdjust(new Date(et,Z,1-P)),F=0;z>F;F++){for(M+="",E=d?"":"",x=0;7>x;x++)O=g?g.apply(e.input?e.input[0]:null,[H]):[!0,""],j=H.getMonth()!==Z,W=j&&!y||!O[0]||X&&X>H||$&&H>$,E+="",H.setDate(H.getDate()+1),H=this._daylightSavingAdjust(H);M+=E+""}Z++,Z>11&&(Z=0,et++),M+="
      "+this._get(e,"weekHeader")+"=5?" class='ui-datepicker-week-end'":"")+">"+""+p[N]+"
      "+this._get(e,"calculateWeek")(H)+""+(j&&!v?" ":W?""+H.getDate()+"":""+H.getDate()+"")+"
      "+(Q?"
      "+(K[0]>0&&T===K[1]-1?"
      ":""):""),k+=M}_+=k}return _+=l,e._keyEvent=!1,_},_generateMonthYearHeader:function(e,t,i,s,n,a,o,r){var h,l,u,d,c,p,f,m,g=this._get(e,"changeMonth"),v=this._get(e,"changeYear"),y=this._get(e,"showMonthAfterYear"),b="
      ",_="";if(a||!g)_+=""+o[t]+"";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,_+=""}if(y||(b+=_+(!a&&g&&v?"":" ")),!e.yearshtml)if(e.yearshtml="",a||!v)b+=""+i+"";else{for(d=this._get(e,"yearRange").split(":"),c=(new Date).getFullYear(),p=function(e){var t=e.match(/c[+\-].*/)?i+parseInt(e.substring(1),10):e.match(/[+\-].*/)?c+parseInt(e,10):parseInt(e,10);return isNaN(t)?c:t},f=p(d[0]),m=Math.max(f,p(d[1]||"")),f=s?Math.max(f,s.getFullYear()):f,m=n?Math.min(m,n.getFullYear()):m,e.yearshtml+="",b+=e.yearshtml,e.yearshtml=null}return b+=this._get(e,"yearSuffix"),y&&(b+=(!a&&g&&v?"":" ")+_),b+="
      "},_adjustInstDate:function(e,t,i){var s=e.drawYear+("Y"===i?t:0),n=e.drawMonth+("M"===i?t:0),a=Math.min(e.selectedDay,this._getDaysInMonth(s,n))+("D"===i?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(s,n,a)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(e)},_restrictMinMax:function(e,t){var i=this._getMinMaxDate(e,"min"),s=this._getMinMaxDate(e,"max"),n=i&&i>t?i:t;return s&&n>s?s:n},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return null==t?[1,1]:"number"==typeof t?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return new Date(e,t,1).getDay()},_canAdjustMonth:function(e,t,i,s){var n=this._getNumberOfMonths(e),a=this._daylightSavingAdjust(new Date(i,s+(0>t?t:n[0]*n[1]),1));return 0>t&&a.setDate(this._getDaysInMonth(a.getFullYear(),a.getMonth())),this._isInRange(e,a)},_isInRange:function(e,t){var i,s,n=this._getMinMaxDate(e,"min"),a=this._getMinMaxDate(e,"max"),o=null,r=null,h=this._get(e,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),o=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(o+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||t.getTime()>=n.getTime())&&(!a||t.getTime()<=a.getTime())&&(!o||t.getFullYear()>=o)&&(!r||r>=t.getFullYear())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t="string"!=typeof t?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,i,s){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var n=t?"object"==typeof t?t:this._daylightSavingAdjust(new Date(s,i,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),n,this._getFormatConfig(e))}}),e.fn.datepicker=function(t){if(!this.length)return this;e.datepicker.initialized||(e(document).mousedown(e.datepicker._checkExternalClick),e.datepicker.initialized=!0),0===e("#"+e.datepicker._mainDivId).length&&e("body").append(e.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof t||"isDisabled"!==t&&"getDate"!==t&&"widget"!==t?"option"===t&&2===arguments.length&&"string"==typeof arguments[1]?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof t?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this].concat(i)):e.datepicker._attachDatepicker(this,t)}):e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i))},e.datepicker=new n,e.datepicker.initialized=!1,e.datepicker.uuid=(new Date).getTime(),e.datepicker.version="1.11.2",e.datepicker,e.widget("ui.draggable",e.ui.mouse,{version:"1.11.2",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._setHandleClassName(),this._mouseInit()},_setOption:function(e,t){this._super(e,t),"handle"===e&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(t){var i=this.options;return this._blurActiveElement(t),this.helper||i.disabled||e(t.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(t),this.handle?(this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(t){this.iframeBlocks=this.document.find(t).map(function(){var t=e(this);return e("
      ").css("position","absolute").appendTo(t.parent()).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(t){var i=this.document[0];if(this.handleElement.is(t.target))try{i.activeElement&&"body"!==i.activeElement.nodeName.toLowerCase()&&e(i.activeElement).blur()}catch(s){}},_mouseStart:function(t){var i=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===e(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(t),this.originalPosition=this.position=this._generatePosition(t,!1),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._normalizeRightBottom(),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_refreshOffsets:function(e){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:e.pageX-this.offset.left,top:e.pageY-this.offset.top}},_mouseDrag:function(t,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(t,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",t,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var i=this,s=!1;return e.ui.ddmanager&&!this.options.dropBehaviour&&(s=e.ui.ddmanager.drop(this,t)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",t)!==!1&&i._clear()}):this._trigger("stop",t)!==!1&&this._clear(),!1},_mouseUp:function(t){return this._unblockFrames(),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),this.handleElement.is(t.target)&&this.element.focus(),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){return this.options.handle?!!e(t.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this.handleElement.addClass("ui-draggable-handle")},_removeHandleClassName:function(){this.handleElement.removeClass("ui-draggable-handle")},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper),n=s?e(i.helper.apply(this.element[0],[t])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_isRootNode:function(e){return/(html|body)/i.test(e.tagName)||e===this.document[0]},_getParentOffset:function(){var t=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var e=this.element.position(),t=this._isRootNode(this.scrollParent[0]);return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+(t?0:this.scrollParent.scrollTop()),left:e.left-(parseInt(this.helper.css("left"),10)||0)+(t?0:this.scrollParent.scrollLeft())}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,n=this.options,a=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,e(window).scrollLeft()+e(window).width()-this.helperProportions.width-this.margins.left,e(window).scrollTop()+(e(window).height()||a.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,e(a).width()-this.helperProportions.width-this.margins.left,(e(a).height()||a.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=e(n.containment),s=i[0],s&&(t=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(t?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(t?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0) -},_convertPositionTo:function(e,t){t||(t=this.position);var i="absolute"===e?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:t.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:t.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(e,t){var i,s,n,a,o=this.options,r=this._isRootNode(this.scrollParent[0]),h=e.pageX,l=e.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),t&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.lefti[2]&&(h=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),o.grid&&(n=o.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-o.grid[1]:n+o.grid[1]:n,a=o.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,h=i?a-this.offset.click.left>=i[0]||a-this.offset.click.left>i[2]?a:a-this.offset.click.left>=i[0]?a-o.grid[0]:a+o.grid[0]:a),"y"===o.axis&&(h=this.originalPageX),"x"===o.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_normalizeRightBottom:function(){"y"!==this.options.axis&&"auto"!==this.helper.css("right")&&(this.helper.width(this.helper.width()),this.helper.css("right","auto")),"x"!==this.options.axis&&"auto"!==this.helper.css("bottom")&&(this.helper.height(this.helper.height()),this.helper.css("bottom","auto"))},_trigger:function(t,i,s){return s=s||this._uiHash(),e.ui.plugin.call(this,t,[i,s,this],!0),/^(drag|start|stop)/.test(t)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),e.Widget.prototype._trigger.call(this,t,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,i,s){var n=e.extend({},i,{item:s.element});s.sortables=[],e(s.options.connectToSortable).each(function(){var i=e(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",t,n))})},stop:function(t,i,s){var n=e.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,e.each(s.sortables,function(){var e=this;e.isOver?(e.isOver=0,s.cancelHelperRemoval=!0,e.cancelHelperRemoval=!1,e._storedCSS={position:e.placeholder.css("position"),top:e.placeholder.css("top"),left:e.placeholder.css("left")},e._mouseStop(t),e.options.helper=e.options._helper):(e.cancelHelperRemoval=!0,e._trigger("deactivate",t,n))})},drag:function(t,i,s){e.each(s.sortables,function(){var n=!1,a=this;a.positionAbs=s.positionAbs,a.helperProportions=s.helperProportions,a.offset.click=s.offset.click,a._intersectsWith(a.containerCache)&&(n=!0,e.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==a&&this._intersectsWith(this.containerCache)&&e.contains(a.element[0],this.element[0])&&(n=!1),n})),n?(a.isOver||(a.isOver=1,a.currentItem=i.helper.appendTo(a.element).data("ui-sortable-item",!0),a.options._helper=a.options.helper,a.options.helper=function(){return i.helper[0]},t.target=a.currentItem[0],a._mouseCapture(t,!0),a._mouseStart(t,!0,!0),a.offset.click.top=s.offset.click.top,a.offset.click.left=s.offset.click.left,a.offset.parent.left-=s.offset.parent.left-a.offset.parent.left,a.offset.parent.top-=s.offset.parent.top-a.offset.parent.top,s._trigger("toSortable",t),s.dropped=a.element,e.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,a.fromOutside=s),a.currentItem&&(a._mouseDrag(t),i.position=a.position)):a.isOver&&(a.isOver=0,a.cancelHelperRemoval=!0,a.options._revert=a.options.revert,a.options.revert=!1,a._trigger("out",t,a._uiHash(a)),a._mouseStop(t,!0),a.options.revert=a.options._revert,a.options.helper=a.options._helper,a.placeholder&&a.placeholder.remove(),s._refreshOffsets(t),i.position=s._generatePosition(t,!0),s._trigger("fromSortable",t),s.dropped=!1,e.each(s.sortables,function(){this.refreshPositions()}))})}}),e.ui.plugin.add("draggable","cursor",{start:function(t,i,s){var n=e("body"),a=s.options;n.css("cursor")&&(a._cursor=n.css("cursor")),n.css("cursor",a.cursor)},stop:function(t,i,s){var n=s.options;n._cursor&&e("body").css("cursor",n._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,i,s){var n=e(i.helper),a=s.options;n.css("opacity")&&(a._opacity=n.css("opacity")),n.css("opacity",a.opacity)},stop:function(t,i,s){var n=s.options;n._opacity&&e(i.helper).css("opacity",n._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(e,t,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(t,i,s){var n=s.options,a=!1,o=s.scrollParentNotHidden[0],r=s.document[0];o!==r&&"HTML"!==o.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+o.offsetHeight-t.pageY=0;c--)h=s.snapElements[c].left-s.margins.left,l=h+s.snapElements[c].width,u=s.snapElements[c].top-s.margins.top,d=u+s.snapElements[c].height,h-m>v||g>l+m||u-m>b||y>d+m||!e.contains(s.snapElements[c].item.ownerDocument,s.snapElements[c].item)?(s.snapElements[c].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,t,e.extend(s._uiHash(),{snapItem:s.snapElements[c].item})),s.snapElements[c].snapping=!1):("inner"!==f.snapMode&&(n=m>=Math.abs(u-b),a=m>=Math.abs(d-y),o=m>=Math.abs(h-v),r=m>=Math.abs(l-g),n&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.top=s._convertPositionTo("relative",{top:d,left:0}).top),o&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),p=n||a||o||r,"outer"!==f.snapMode&&(n=m>=Math.abs(u-y),a=m>=Math.abs(d-b),o=m>=Math.abs(h-g),r=m>=Math.abs(l-v),n&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.top=s._convertPositionTo("relative",{top:d-s.helperProportions.height,left:0}).top),o&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[c].snapping&&(n||a||o||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,t,e.extend(s._uiHash(),{snapItem:s.snapElements[c].item})),s.snapElements[c].snapping=n||a||o||r||p)}}),e.ui.plugin.add("draggable","stack",{start:function(t,i,s){var n,a=s.options,o=e.makeArray(e(a.stack)).sort(function(t,i){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(i).css("zIndex"),10)||0)});o.length&&(n=parseInt(e(o[0]).css("zIndex"),10)||0,e(o).each(function(t){e(this).css("zIndex",n+t)}),this.css("zIndex",n+o.length))}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,i,s){var n=e(i.helper),a=s.options;n.css("zIndex")&&(a._zIndex=n.css("zIndex")),n.css("zIndex",a.zIndex)},stop:function(t,i,s){var n=s.options;n._zIndex&&e(i.helper).css("zIndex",n._zIndex)}}),e.ui.draggable,e.widget("ui.resizable",e.ui.mouse,{version:"1.11.2",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(e){return parseInt(e,10)||0},_isNumber:function(e){return!isNaN(parseInt(e,10))},_hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return t[s]>0?!0:(t[s]=1,n=t[s]>0,t[s]=0,n)},_create:function(){var t,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(e("
      ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),t=this.handles.split(","),this.handles={},i=0;t.length>i;i++)s=e.trim(t[i]),a="ui-resizable-"+s,n=e("
      "),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(t){var i,s,n,a;t=t||this.element;for(i in this.handles)this.handles[i].constructor===String&&(this.handles[i]=this.element.children(this.handles[i]).first().show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(s=e(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),t.css(n,a),this._proportionallyResize()),e(this.handles[i]).length},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(e(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(e(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t,i=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),t=this.element,this.originalElement.css({position:t.css("position"),width:t.outerWidth(),height:t.outerHeight(),top:t.css("top"),left:t.css("left")}).insertAfter(t),t.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(t){var i,s,n=!1;for(i in this.handles)s=e(this.handles[i])[0],(s===t.target||e.contains(s,t.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var i,s,n,a=this.options,o=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),a.containment&&(i+=e(a.containment).scrollLeft()||0,s+=e(a.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:o.width(),height:o.height()},this.originalSize=this._helper?{width:o.outerWidth(),height:o.outerHeight()}:{width:o.width(),height:o.height()},this.sizeDiff={width:o.outerWidth()-o.width(),height:o.outerHeight()-o.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof a.aspectRatio?a.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=e(".ui-resizable-"+this.axis).css("cursor"),e("body").css("cursor","auto"===n?this.axis+"-resize":n),o.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var i,s,n=this.originalMousePosition,a=this.axis,o=t.pageX-n.left||0,r=t.pageY-n.top||0,h=this._change[a];return this._updatePrevProperties(),h?(i=h.apply(this,[t,o,r]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(i=this._updateRatio(i,t)),i=this._respectSize(i,t),this._updateCache(i),this._propagate("resize",t),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),e.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(t){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,u=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:u.sizeDiff.height,a=s?0:u.sizeDiff.width,o={width:u.helper.width()-a,height:u.helper.height()-n},r=parseInt(u.element.css("left"),10)+(u.position.left-u.originalPosition.left)||null,h=parseInt(u.element.css("top"),10)+(u.position.top-u.originalPosition.top)||null,l.animate||this.element.css(e.extend(o,{top:h,left:r})),u.helper.height(u.size.height),u.helper.width(u.size.width),this._helper&&!l.animate&&this._proportionallyResize()),e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var e={};return this.position.top!==this.prevPosition.top&&(e.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(e.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(e.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(e.height=this.size.height+"px"),this.helper.css(e),e},_updateVirtualBoundaries:function(e){var t,i,s,n,a,o=this.options;a={minWidth:this._isNumber(o.minWidth)?o.minWidth:0,maxWidth:this._isNumber(o.maxWidth)?o.maxWidth:1/0,minHeight:this._isNumber(o.minHeight)?o.minHeight:0,maxHeight:this._isNumber(o.maxHeight)?o.maxHeight:1/0},(this._aspectRatio||e)&&(t=a.minHeight*this.aspectRatio,s=a.minWidth/this.aspectRatio,i=a.maxHeight*this.aspectRatio,n=a.maxWidth/this.aspectRatio,t>a.minWidth&&(a.minWidth=t),s>a.minHeight&&(a.minHeight=s),a.maxWidth>i&&(a.maxWidth=i),a.maxHeight>n&&(a.maxHeight=n)),this._vBoundaries=a},_updateCache:function(e){this.offset=this.helper.offset(),this._isNumber(e.left)&&(this.position.left=e.left),this._isNumber(e.top)&&(this.position.top=e.top),this._isNumber(e.height)&&(this.size.height=e.height),this._isNumber(e.width)&&(this.size.width=e.width)},_updateRatio:function(e){var t=this.position,i=this.size,s=this.axis;return this._isNumber(e.height)?e.width=e.height*this.aspectRatio:this._isNumber(e.width)&&(e.height=e.width/this.aspectRatio),"sw"===s&&(e.left=t.left+(i.width-e.width),e.top=null),"nw"===s&&(e.top=t.top+(i.height-e.height),e.left=t.left+(i.width-e.width)),e},_respectSize:function(e){var t=this._vBoundaries,i=this.axis,s=this._isNumber(e.width)&&t.maxWidth&&t.maxWidthe.width,o=this._isNumber(e.height)&&t.minHeight&&t.minHeight>e.height,r=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,l=/sw|nw|w/.test(i),u=/nw|ne|n/.test(i);return a&&(e.width=t.minWidth),o&&(e.height=t.minHeight),s&&(e.width=t.maxWidth),n&&(e.height=t.maxHeight),a&&l&&(e.left=r-t.minWidth),s&&l&&(e.left=r-t.maxWidth),o&&u&&(e.top=h-t.minHeight),n&&u&&(e.top=h-t.maxHeight),e.width||e.height||e.left||!e.top?e.width||e.height||e.top||!e.left||(e.left=null):e.top=null,e},_getPaddingPlusBorderDimensions:function(e){for(var t=0,i=[],s=[e.css("borderTopWidth"),e.css("borderRightWidth"),e.css("borderBottomWidth"),e.css("borderLeftWidth")],n=[e.css("paddingTop"),e.css("paddingRight"),e.css("paddingBottom"),e.css("paddingLeft")];4>t;t++)i[t]=parseInt(s[t],10)||0,i[t]+=parseInt(n[t],10)||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var e,t=0,i=this.helper||this.element;this._proportionallyResizeElements.length>t;t++)e=this._proportionallyResizeElements[t],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(e)),e.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var t=this.element,i=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||e("
      "),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(e,t){return{width:this.originalSize.width+t}},w:function(e,t){var i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(e,t,i){return{height:this.originalSize.height+i}},se:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},sw:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,i,s]))},ne:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},nw:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,i,s]))}},_propagate:function(t,i){e.ui.plugin.call(this,t,[i,this.ui()]),"resize"!==t&&this._trigger(t,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","animate",{stop:function(t){var i=e(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,u=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(e.extend(h,u&&l?{top:u,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&e(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(){var t,i,s,n,a,o,r,h=e(this).resizable("instance"),l=h.options,u=h.element,d=l.containment,c=d instanceof e?d.get(0):/parent/.test(d)?u.parent().get(0):d;c&&(h.containerElement=e(c),/document/.test(d)||d===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}):(t=e(c),i=[],e(["Top","Right","Left","Bottom"]).each(function(e,s){i[e]=h._num(t.css("padding"+s))}),h.containerOffset=t.offset(),h.containerPosition=t.position(),h.containerSize={height:t.innerHeight()-i[3],width:t.innerWidth()-i[1]},s=h.containerOffset,n=h.containerSize.height,a=h.containerSize.width,o=h._hasScroll(c,"left")?c.scrollWidth:a,r=h._hasScroll(c)?c.scrollHeight:n,h.parentData={element:c,left:s.left,top:s.top,width:o,height:r}))},resize:function(t){var i,s,n,a,o=e(this).resizable("instance"),r=o.options,h=o.containerOffset,l=o.position,u=o._aspectRatio||t.shiftKey,d={top:0,left:0},c=o.containerElement,p=!0;c[0]!==document&&/static/.test(c.css("position"))&&(d=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-d.left),u&&(o.size.height=o.size.width/o.aspectRatio,p=!1),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),u&&(o.size.width=o.size.height*o.aspectRatio,p=!1),o.position.top=o._helper?h.top:0),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a?(o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top):(o.offset.left=o.element.offset().left,o.offset.top=o.element.offset().top),i=Math.abs(o.sizeDiff.width+(o._helper?o.offset.left-d.left:o.offset.left-h.left)),s=Math.abs(o.sizeDiff.height+(o._helper?o.offset.top-d.top:o.offset.top-h.top)),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,u&&(o.size.height=o.size.width/o.aspectRatio,p=!1)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,u&&(o.size.width=o.size.height*o.aspectRatio,p=!1)),p||(o.position.left=o.prevPosition.left,o.position.top=o.prevPosition.top,o.size.width=o.prevSize.width,o.size.height=o.prevSize.height)},stop:function(){var t=e(this).resizable("instance"),i=t.options,s=t.containerOffset,n=t.containerPosition,a=t.containerElement,o=e(t.helper),r=o.offset(),h=o.outerWidth()-t.sizeDiff.width,l=o.outerHeight()-t.sizeDiff.height;t._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l}),t._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),e.ui.plugin.add("resizable","alsoResize",{start:function(){var t=e(this).resizable("instance"),i=t.options,s=function(t){e(t).each(function(){var t=e(this);t.data("ui-resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};"object"!=typeof i.alsoResize||i.alsoResize.parentNode?s(i.alsoResize):i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):e.each(i.alsoResize,function(e){s(e)})},resize:function(t,i){var s=e(this).resizable("instance"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0},h=function(t,s){e(t).each(function(){var t=e(this),n=e(this).data("ui-resizable-alsoresize"),a={},o=s&&s.length?s:t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var i=(n[t]||0)+(r[t]||0);i&&i>=0&&(a[t]=i||null)}),t.css(a)})};"object"!=typeof n.alsoResize||n.alsoResize.nodeType?h(n.alsoResize):e.each(n.alsoResize,function(e,t){h(e,t)})},stop:function(){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","ghost",{start:function(){var t=e(this).resizable("instance"),i=t.options,s=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),t.ghost.appendTo(t.helper)},resize:function(){var t=e(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=e(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(){var t,i=e(this).resizable("instance"),s=i.options,n=i.size,a=i.originalSize,o=i.originalPosition,r=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,u=h[1]||1,d=Math.round((n.width-a.width)/l)*l,c=Math.round((n.height-a.height)/u)*u,p=a.width+d,f=a.height+c,m=s.maxWidth&&p>s.maxWidth,g=s.maxHeight&&f>s.maxHeight,v=s.minWidth&&s.minWidth>p,y=s.minHeight&&s.minHeight>f;s.grid=h,v&&(p+=l),y&&(f+=u),m&&(p-=l),g&&(f-=u),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=o.top-c):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=o.left-d):((0>=f-u||0>=p-l)&&(t=i._getPaddingPlusBorderDimensions(this)),f-u>0?(i.size.height=f,i.position.top=o.top-c):(f=u-t.height,i.size.height=f,i.position.top=o.top+a.height-f),p-l>0?(i.size.width=p,i.position.left=o.left-d):(p=u-t.height,i.size.width=p,i.position.left=o.left+a.width-p))}}),e.ui.resizable,e.widget("ui.dialog",{version:"1.11.2",options:{appendTo:"body",autoOpen:!0,buttons:[],closeOnEscape:!0,closeText:"Close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var i=e(this).css(t).offset().top;0>i&&e(this).css("top",t.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),this.options.title=this.options.title||this.originalTitle,this._createWrapper(),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&e.fn.draggable&&this._makeDraggable(),this.options.resizable&&e.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var t=this.options.appendTo;return t&&(t.jquery||t.nodeType)?e(t):this.document.find(t||"body").eq(0)},_destroy:function(){var e,t=this.originalPosition;this._destroyOverlay(),this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach(),this.uiDialog.stop(!0,!0).remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},disable:e.noop,enable:e.noop,close:function(t){var i,s=this;if(this._isOpen&&this._trigger("beforeClose",t)!==!1){if(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),!this.opener.filter(":focusable").focus().length)try{i=this.document[0].activeElement,i&&"body"!==i.nodeName.toLowerCase()&&e(i).blur()}catch(n){}this._hide(this.uiDialog,this.options.hide,function(){s._trigger("close",t)})}},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(t,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+e(this).css("z-index")}).get(),a=Math.max.apply(null,n);return a>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",a+1),s=!0),s&&!i&&this._trigger("focus",t),s},open:function(){var t=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=e(this.document[0].activeElement),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){t._focusTabbable(),t._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var e=this._focusedElement;e||(e=this.element.find("[autofocus]")),e.length||(e=this.element.find(":tabbable")),e.length||(e=this.uiDialogButtonPane.find(":tabbable")),e.length||(e=this.uiDialogTitlebarClose.filter(":tabbable")),e.length||(e=this.uiDialog),e.eq(0).focus()},_keepFocus:function(t){function i(){var t=this.document[0].activeElement,i=this.uiDialog[0]===t||e.contains(this.uiDialog[0],t);i||this._focusTabbable()}t.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=e("
      ").addClass("ui-dialog ui-widget ui-widget-content ui-corner-all ui-front "+this.options.dialogClass).hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._on(this.uiDialog,{keydown:function(t){if(this.options.closeOnEscape&&!t.isDefaultPrevented()&&t.keyCode&&t.keyCode===e.ui.keyCode.ESCAPE)return t.preventDefault(),this.close(t),void 0; -if(t.keyCode===e.ui.keyCode.TAB&&!t.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");t.target!==n[0]&&t.target!==this.uiDialog[0]||t.shiftKey?t.target!==s[0]&&t.target!==this.uiDialog[0]||!t.shiftKey||(this._delay(function(){n.focus()}),t.preventDefault()):(this._delay(function(){s.focus()}),t.preventDefault())}},mousedown:function(e){this._moveToTop(e)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var t;this.uiDialogTitlebar=e("
      ").addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(this.uiDialog),this._on(this.uiDialogTitlebar,{mousedown:function(t){e(t.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.focus()}}),this.uiDialogTitlebarClose=e("").button({label:this.options.closeText,icons:{primary:"ui-icon-closethick"},text:!1}).addClass("ui-dialog-titlebar-close").appendTo(this.uiDialogTitlebar),this._on(this.uiDialogTitlebarClose,{click:function(e){e.preventDefault(),this.close(e)}}),t=e("").uniqueId().addClass("ui-dialog-title").prependTo(this.uiDialogTitlebar),this._title(t),this.uiDialog.attr({"aria-labelledby":t.attr("id")})},_title:function(e){this.options.title||e.html(" "),e.text(this.options.title)},_createButtonPane:function(){this.uiDialogButtonPane=e("
      ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),this.uiButtonSet=e("
      ").addClass("ui-dialog-buttonset").appendTo(this.uiDialogButtonPane),this._createButtons()},_createButtons:function(){var t=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),e.isEmptyObject(i)||e.isArray(i)&&!i.length?(this.uiDialog.removeClass("ui-dialog-buttons"),void 0):(e.each(i,function(i,s){var n,a;s=e.isFunction(s)?{click:s,text:i}:s,s=e.extend({type:"button"},s),n=s.click,s.click=function(){n.apply(t.element[0],arguments)},a={icons:s.icons,text:s.showText},delete s.icons,delete s.showText,e("",s).button(a).appendTo(t.uiButtonSet)}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function t(e){return{position:e.position,offset:e.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){e(this).addClass("ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,t(n))},drag:function(e,s){i._trigger("drag",e,t(s))},stop:function(n,a){var o=a.offset.left-i.document.scrollLeft(),r=a.offset.top-i.document.scrollTop();s.position={my:"left top",at:"left"+(o>=0?"+":"")+o+" "+"top"+(r>=0?"+":"")+r,of:i.window},e(this).removeClass("ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,t(a))}})},_makeResizable:function(){function t(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}var i=this,s=this.options,n=s.resizable,a=this.uiDialog.css("position"),o="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:o,start:function(s,n){e(this).addClass("ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,t(n))},resize:function(e,s){i._trigger("resize",e,t(s))},stop:function(n,a){var o=i.uiDialog.offset(),r=o.left-i.document.scrollLeft(),h=o.top-i.document.scrollTop();s.height=i.uiDialog.height(),s.width=i.uiDialog.width(),s.position={my:"left top",at:"left"+(r>=0?"+":"")+r+" "+"top"+(h>=0?"+":"")+h,of:i.window},e(this).removeClass("ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,t(a))}}).css("position",a)},_trackFocus:function(){this._on(this.widget(),{focusin:function(t){this._makeFocusTarget(),this._focusedElement=e(t.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var t=this._trackingInstances(),i=e.inArray(this,t);-1!==i&&t.splice(i,1)},_trackingInstances:function(){var e=this.document.data("ui-dialog-instances");return e||(e=[],this.document.data("ui-dialog-instances",e)),e},_minHeight:function(){var e=this.options;return"auto"===e.height?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(){var e=this.uiDialog.is(":visible");e||this.uiDialog.show(),this.uiDialog.position(this.options.position),e||this.uiDialog.hide()},_setOptions:function(t){var i=this,s=!1,n={};e.each(t,function(e,t){i._setOption(e,t),e in i.sizeRelatedOptions&&(s=!0),e in i.resizableRelatedOptions&&(n[e]=t)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(e,t){var i,s,n=this.uiDialog;"dialogClass"===e&&n.removeClass(this.options.dialogClass).addClass(t),"disabled"!==e&&(this._super(e,t),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:""+t}),"draggable"===e&&(i=n.is(":data(ui-draggable)"),i&&!t&&n.draggable("destroy"),!i&&t&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(s=n.is(":data(ui-resizable)"),s&&!t&&n.resizable("destroy"),s&&"string"==typeof t&&n.resizable("option","handles",t),s||t===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var e,t,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),e=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),t=Math.max(0,s.minHeight-e),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-e):"none","auto"===s.height?this.element.css({minHeight:t,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-e)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var t=e(this);return e("
      ").css({position:"absolute",width:t.outerWidth(),height:t.outerHeight()}).appendTo(t.parent()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(t){return e(t.target).closest(".ui-dialog").length?!0:!!e(t.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var t=!0;this._delay(function(){t=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(e){t||this._allowInteraction(e)||(e.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=e("
      ").addClass("ui-widget-overlay ui-front").appendTo(this._appendTo()),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var e=this.document.data("ui-dialog-overlays")-1;e?this.document.data("ui-dialog-overlays",e):this.document.unbind("focusin").removeData("ui-dialog-overlays"),this.overlay.remove(),this.overlay=null}}}),e.widget("ui.droppable",{version:"1.11.2",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var t,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=e.isFunction(s)?s:function(e){return e.is(s)},this.proportions=function(){return arguments.length?(t=arguments[0],void 0):t?t:t={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this.element.addClass("ui-droppable")},_addToManager:function(t){e.ui.ddmanager.droppables[t]=e.ui.ddmanager.droppables[t]||[],e.ui.ddmanager.droppables[t].push(this)},_splice:function(e){for(var t=0;e.length>t;t++)e[t]===this&&e.splice(t,1)},_destroy:function(){var t=e.ui.ddmanager.droppables[this.options.scope];this._splice(t),this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(t,i){if("accept"===t)this.accept=e.isFunction(i)?i:function(e){return e.is(i)};else if("scope"===t){var s=e.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(t,i)},_activate:function(t){var i=e.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),i&&this._trigger("activate",t,this.ui(i))},_deactivate:function(t){var i=e.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),i&&this._trigger("deactivate",t,this.ui(i))},_over:function(t){var i=e.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",t,this.ui(i)))},_out:function(t){var i=e.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",t,this.ui(i)))},_drop:function(t,i){var s=i||e.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=e(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&e.ui.intersect(s,e.extend(i,{offset:i.element.offset()}),i.options.tolerance,t)?(n=!0,!1):void 0}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",t,this.ui(s)),this.element):!1):!1},ui:function(e){return{draggable:e.currentItem||e.element,helper:e.helper,position:e.position,offset:e.positionAbs}}}),e.ui.intersect=function(){function e(e,t,i){return e>=t&&t+i>e}return function(t,i,s,n){if(!i.offset)return!1;var a=(t.positionAbs||t.position.absolute).left+t.margins.left,o=(t.positionAbs||t.position.absolute).top+t.margins.top,r=a+t.helperProportions.width,h=o+t.helperProportions.height,l=i.offset.left,u=i.offset.top,d=l+i.proportions().width,c=u+i.proportions().height;switch(s){case"fit":return a>=l&&d>=r&&o>=u&&c>=h;case"intersect":return a+t.helperProportions.width/2>l&&d>r-t.helperProportions.width/2&&o+t.helperProportions.height/2>u&&c>h-t.helperProportions.height/2;case"pointer":return e(n.pageY,u,i.proportions().height)&&e(n.pageX,l,i.proportions().width);case"touch":return(o>=u&&c>=o||h>=u&&c>=h||u>o&&h>c)&&(a>=l&&d>=a||r>=l&&d>=r||l>a&&r>d);default:return!1}}}(),e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,i){var s,n,a=e.ui.ddmanager.droppables[t.options.scope]||[],o=i?i.type:null,r=(t.currentItem||t.element).find(":data(ui-droppable)").addBack();e:for(s=0;a.length>s;s++)if(!(a[s].options.disabled||t&&!a[s].accept.call(a[s].element[0],t.currentItem||t.element))){for(n=0;r.length>n;n++)if(r[n]===a[s].element[0]){a[s].proportions().height=0;continue e}a[s].visible="none"!==a[s].element.css("display"),a[s].visible&&("mousedown"===o&&a[s]._activate.call(a[s],i),a[s].offset=a[s].element.offset(),a[s].proportions({width:a[s].element[0].offsetWidth,height:a[s].element[0].offsetHeight}))}},drop:function(t,i){var s=!1;return e.each((e.ui.ddmanager.droppables[t.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&e.ui.intersect(t,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],t.currentItem||t.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(t,i){t.element.parentsUntil("body").bind("scroll.droppable",function(){t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,i)})},drag:function(t,i){t.options.refreshPositions&&e.ui.ddmanager.prepareOffsets(t,i),e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,a,o=e.ui.intersect(t,this,this.options.tolerance,i),r=!o&&this.isover?"isout":o&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,a=this.element.parents(":data(ui-droppable)").filter(function(){return e(this).droppable("instance").options.scope===n}),a.length&&(s=e(a[0]).droppable("instance"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(t,i){t.element.parentsUntil("body").unbind("scroll.droppable"),t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,i)}},e.ui.droppable;var y="ui-effects-",b=e;e.effects={effect:{}},function(e,t){function i(e,t,i){var s=d[t.type]||{};return null==e?i||!t.def?null:t.def:(e=s.floor?~~e:parseFloat(e),isNaN(e)?t.def:s.mod?(e+s.mod)%s.mod:0>e?0:e>s.max?s.max:e)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(e,a){var o,r=a.re.exec(i),h=r&&a.parse(r),l=a.space||"rgba";return h?(o=s[l](h),s[u[l].cache]=o[u[l].cache],n=s._rgba=o._rgba,!1):t}),n.length?("0,0,0,0"===n.join()&&e.extend(n,a.transparent),s):a[i]}function n(e,t,i){return i=(i+1)%1,1>6*i?e+6*(t-e)*i:1>2*i?t:2>3*i?e+6*(t-e)*(2/3-i):e}var a,o="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1],e[2],e[3],e[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[2.55*e[1],2.55*e[2],2.55*e[3],e[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(e){return[e[1],e[2]/100,e[3]/100,e[4]]}}],l=e.Color=function(t,i,s,n){return new e.Color.fn.parse(t,i,s,n)},u={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},d={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},c=l.support={},p=e("

      ")[0],f=e.each;p.style.cssText="background-color:rgba(1,1,1,.5)",c.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),l.fn=e.extend(l.prototype,{parse:function(n,o,r,h){if(n===t)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=e(n).css(o),o=t);var d=this,c=e.type(n),p=this._rgba=[];return o!==t&&(n=[n,o,r,h],c="array"),"string"===c?this.parse(s(n)||a._default):"array"===c?(f(u.rgba.props,function(e,t){p[t.idx]=i(n[t.idx],t)}),this):"object"===c?(n instanceof l?f(u,function(e,t){n[t.cache]&&(d[t.cache]=n[t.cache].slice())}):f(u,function(t,s){var a=s.cache;f(s.props,function(e,t){if(!d[a]&&s.to){if("alpha"===e||null==n[e])return;d[a]=s.to(d._rgba)}d[a][t.idx]=i(n[e],t,!0)}),d[a]&&0>e.inArray(null,d[a].slice(0,3))&&(d[a][3]=1,s.from&&(d._rgba=s.from(d[a])))}),this):t},is:function(e){var i=l(e),s=!0,n=this;return f(u,function(e,a){var o,r=i[a.cache];return r&&(o=n[a.cache]||a.to&&a.to(n._rgba)||[],f(a.props,function(e,i){return null!=r[i.idx]?s=r[i.idx]===o[i.idx]:t})),s}),s},_space:function(){var e=[],t=this;return f(u,function(i,s){t[s.cache]&&e.push(i)}),e.pop()},transition:function(e,t){var s=l(e),n=s._space(),a=u[n],o=0===this.alpha()?l("transparent"):this,r=o[a.cache]||a.to(o._rgba),h=r.slice();return s=s[a.cache],f(a.props,function(e,n){var a=n.idx,o=r[a],l=s[a],u=d[n.type]||{};null!==l&&(null===o?h[a]=l:(u.mod&&(l-o>u.mod/2?o+=u.mod:o-l>u.mod/2&&(o-=u.mod)),h[a]=i((l-o)*t+o,n)))}),this[n](h)},blend:function(t){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(t)._rgba;return l(e.map(i,function(e,t){return(1-s)*n[t]+s*e}))},toRgbaString:function(){var t="rgba(",i=e.map(this._rgba,function(e,t){return null==e?t>2?1:0:e});return 1===i[3]&&(i.pop(),t="rgb("),t+i.join()+")"},toHslaString:function(){var t="hsla(",i=e.map(this.hsla(),function(e,t){return null==e&&(e=t>2?1:0),t&&3>t&&(e=Math.round(100*e)+"%"),e});return 1===i[3]&&(i.pop(),t="hsl("),t+i.join()+")"},toHexString:function(t){var i=this._rgba.slice(),s=i.pop();return t&&i.push(~~(255*s)),"#"+e.map(i,function(e){return e=(e||0).toString(16),1===e.length?"0"+e:e}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,u.hsla.to=function(e){if(null==e[0]||null==e[1]||null==e[2])return[null,null,null,e[3]];var t,i,s=e[0]/255,n=e[1]/255,a=e[2]/255,o=e[3],r=Math.max(s,n,a),h=Math.min(s,n,a),l=r-h,u=r+h,d=.5*u;return t=h===r?0:s===r?60*(n-a)/l+360:n===r?60*(a-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=d?l/u:l/(2-u),[Math.round(t)%360,i,d,null==o?1:o]},u.hsla.from=function(e){if(null==e[0]||null==e[1]||null==e[2])return[null,null,null,e[3]];var t=e[0]/360,i=e[1],s=e[2],a=e[3],o=.5>=s?s*(1+i):s+i-s*i,r=2*s-o;return[Math.round(255*n(r,o,t+1/3)),Math.round(255*n(r,o,t)),Math.round(255*n(r,o,t-1/3)),a]},f(u,function(s,n){var a=n.props,o=n.cache,h=n.to,u=n.from;l.fn[s]=function(s){if(h&&!this[o]&&(this[o]=h(this._rgba)),s===t)return this[o].slice();var n,r=e.type(s),d="array"===r||"object"===r?s:arguments,c=this[o].slice();return f(a,function(e,t){var s=d["object"===r?e:t.idx];null==s&&(s=c[t.idx]),c[t.idx]=i(s,t)}),u?(n=l(u(c)),n[o]=c,n):l(c)},f(a,function(t,i){l.fn[t]||(l.fn[t]=function(n){var a,o=e.type(n),h="alpha"===t?this._hsla?"hsla":"rgba":s,l=this[h](),u=l[i.idx];return"undefined"===o?u:("function"===o&&(n=n.call(this,u),o=e.type(n)),null==n&&i.empty?this:("string"===o&&(a=r.exec(n),a&&(n=u+parseFloat(a[2])*("+"===a[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(t){var i=t.split(" ");f(i,function(t,i){e.cssHooks[i]={set:function(t,n){var a,o,r="";if("transparent"!==n&&("string"!==e.type(n)||(a=s(n)))){if(n=l(a||n),!c.rgba&&1!==n._rgba[3]){for(o="backgroundColor"===i?t.parentNode:t;(""===r||"transparent"===r)&&o&&o.style;)try{r=e.css(o,"backgroundColor"),o=o.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{t.style[i]=n}catch(h){}}},e.fx.step[i]=function(t){t.colorInit||(t.start=l(t.elem,i),t.end=l(t.end),t.colorInit=!0),e.cssHooks[i].set(t.elem,t.start.transition(t.end,t.pos))}})},l.hook(o),e.cssHooks.borderColor={expand:function(e){var t={};return f(["Top","Right","Bottom","Left"],function(i,s){t["border"+s+"Color"]=e}),t}},a=e.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(b),function(){function t(t){var i,s,n=t.ownerDocument.defaultView?t.ownerDocument.defaultView.getComputedStyle(t,null):t.currentStyle,a={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(a[e.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(a[i]=n[i]);return a}function i(t,i){var s,a,o={};for(s in i)a=i[s],t[s]!==a&&(n[s]||(e.fx.step[s]||!isNaN(parseFloat(a)))&&(o[s]=a));return o}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,i){e.fx.step[i]=function(e){("none"!==e.end&&!e.setAttr||1===e.pos&&!e.setAttr)&&(b.style(e.elem,i,e.end),e.setAttr=!0)}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e.effects.animateClass=function(n,a,o,r){var h=e.speed(a,o,r);return this.queue(function(){var a,o=e(this),r=o.attr("class")||"",l=h.children?o.find("*").addBack():o;l=l.map(function(){var i=e(this);return{el:i,start:t(this)}}),a=function(){e.each(s,function(e,t){n[t]&&o[t+"Class"](n[t])})},a(),l=l.map(function(){return this.end=t(this.el[0]),this.diff=i(this.start,this.end),this}),o.attr("class",r),l=l.map(function(){var t=this,i=e.Deferred(),s=e.extend({},h,{queue:!1,complete:function(){i.resolve(t)}});return this.el.animate(this.diff,s),i.promise()}),e.when.apply(e,l.get()).done(function(){a(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),h.complete.call(o[0])})})},e.fn.extend({addClass:function(t){return function(i,s,n,a){return s?e.effects.animateClass.call(this,{add:i},s,n,a):t.apply(this,arguments)}}(e.fn.addClass),removeClass:function(t){return function(i,s,n,a){return arguments.length>1?e.effects.animateClass.call(this,{remove:i},s,n,a):t.apply(this,arguments)}}(e.fn.removeClass),toggleClass:function(t){return function(i,s,n,a,o){return"boolean"==typeof s||void 0===s?n?e.effects.animateClass.call(this,s?{add:i}:{remove:i},n,a,o):t.apply(this,arguments):e.effects.animateClass.call(this,{toggle:i},s,n,a)}}(e.fn.toggleClass),switchClass:function(t,i,s,n,a){return e.effects.animateClass.call(this,{add:i,remove:t},s,n,a)}})}(),function(){function t(t,i,s,n){return e.isPlainObject(t)&&(i=t,t=t.effect),t={effect:t},null==i&&(i={}),e.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||e.fx.speeds[i])&&(n=s,s=i,i={}),e.isFunction(s)&&(n=s,s=null),i&&e.extend(t,i),s=s||i.duration,t.duration=e.fx.off?0:"number"==typeof s?s:s in e.fx.speeds?e.fx.speeds[s]:e.fx.speeds._default,t.complete=n||i.complete,t}function i(t){return!t||"number"==typeof t||e.fx.speeds[t]?!0:"string"!=typeof t||e.effects.effect[t]?e.isFunction(t)?!0:"object"!=typeof t||t.effect?!1:!0:!0}e.extend(e.effects,{version:"1.11.2",save:function(e,t){for(var i=0;t.length>i;i++)null!==t[i]&&e.data(y+t[i],e[0].style[t[i]])},restore:function(e,t){var i,s;for(s=0;t.length>s;s++)null!==t[s]&&(i=e.data(y+t[s]),void 0===i&&(i=""),e.css(t[s],i))},setMode:function(e,t){return"toggle"===t&&(t=e.is(":hidden")?"show":"hide"),t},getBaseline:function(e,t){var i,s;switch(e[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=e[0]/t.height}switch(e[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=e[1]/t.width}return{x:s,y:i}},createWrapper:function(t){if(t.parent().is(".ui-effects-wrapper"))return t.parent();var i={width:t.outerWidth(!0),height:t.outerHeight(!0),"float":t.css("float")},s=e("

      ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:t.width(),height:t.height()},a=document.activeElement;try{a.id}catch(o){a=document.body}return t.wrap(s),(t[0]===a||e.contains(t[0],a))&&e(a).focus(),s=t.parent(),"static"===t.css("position")?(s.css({position:"relative"}),t.css({position:"relative"})):(e.extend(i,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,s){i[s]=t.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(n),s.css(i).show()},removeWrapper:function(t){var i=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===i||e.contains(t[0],i))&&e(i).focus()),t},setTransition:function(t,i,s,n){return n=n||{},e.each(i,function(e,i){var a=t.cssUnit(i);a[0]>0&&(n[i]=a[0]*s+a[1])}),n}}),e.fn.extend({effect:function(){function i(t){function i(){e.isFunction(a)&&a.call(n[0]),e.isFunction(t)&&t()}var n=e(this),a=s.complete,r=s.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),i()):o.call(n[0],s,i)}var s=t.apply(this,arguments),n=s.mode,a=s.queue,o=e.effects.effect[s.effect];return e.fx.off||!o?n?this[n](s.duration,s.complete):this.each(function(){s.complete&&s.complete.call(this)}):a===!1?this.each(i):this.queue(a||"fx",i)},show:function(e){return function(s){if(i(s))return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="show",this.effect.call(this,n)}}(e.fn.show),hide:function(e){return function(s){if(i(s))return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(e.fn.hide),toggle:function(e){return function(s){if(i(s)||"boolean"==typeof s)return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(e.fn.toggle),cssUnit:function(t){var i=this.css(t),s=[];return e.each(["em","px","%","pt"],function(e,t){i.indexOf(t)>0&&(s=[parseFloat(i),t])}),s}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,i){t[i]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return 0===e||1===e?e:-Math.pow(2,8*(e-1))*Math.sin((80*(e-1)-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){for(var t,i=4;((t=Math.pow(2,--i))-1)/11>e;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*t-2)/22-e,2)}}),e.each(t,function(t,i){e.easing["easeIn"+t]=i,e.easing["easeOut"+t]=function(e){return 1-i(1-e)},e.easing["easeInOut"+t]=function(e){return.5>e?i(2*e)/2:1-i(-2*e+2)/2}})}(),e.effects,e.effects.effect.blind=function(t,i){var s,n,a,o=e(this),r=/up|down|vertical/,h=/up|left|vertical|horizontal/,l=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(o,t.mode||"hide"),d=t.direction||"up",c=r.test(d),p=c?"height":"width",f=c?"top":"left",m=h.test(d),g={},v="show"===u;o.parent().is(".ui-effects-wrapper")?e.effects.save(o.parent(),l):e.effects.save(o,l),o.show(),s=e.effects.createWrapper(o).css({overflow:"hidden"}),n=s[p](),a=parseFloat(s.css(f))||0,g[p]=v?n:0,m||(o.css(c?"bottom":"right",0).css(c?"top":"left","auto").css({position:"absolute"}),g[f]=v?a:n+a),v&&(s.css(p,0),m||s.css(f,a+n)),s.animate(g,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){"hide"===u&&o.hide(),e.effects.restore(o,l),e.effects.removeWrapper(o),i()}})},e.effects.effect.bounce=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","height","width"],h=e.effects.setMode(o,t.mode||"effect"),l="hide"===h,u="show"===h,d=t.direction||"up",c=t.distance,p=t.times||5,f=2*p+(u||l?1:0),m=t.duration/f,g=t.easing,v="up"===d||"down"===d?"top":"left",y="up"===d||"left"===d,b=o.queue(),_=b.length;for((u||l)&&r.push("opacity"),e.effects.save(o,r),o.show(),e.effects.createWrapper(o),c||(c=o["top"===v?"outerHeight":"outerWidth"]()/3),u&&(a={opacity:1},a[v]=0,o.css("opacity",0).css(v,y?2*-c:2*c).animate(a,m,g)),l&&(c/=Math.pow(2,p-1)),a={},a[v]=0,s=0;p>s;s++)n={},n[v]=(y?"-=":"+=")+c,o.animate(n,m,g).animate(a,m,g),c=l?2*c:c/2;l&&(n={opacity:0},n[v]=(y?"-=":"+=")+c,o.animate(n,m,g)),o.queue(function(){l&&o.hide(),e.effects.restore(o,r),e.effects.removeWrapper(o),i()}),_>1&&b.splice.apply(b,[1,0].concat(b.splice(_,f+1))),o.dequeue()},e.effects.effect.clip=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","height","width"],h=e.effects.setMode(o,t.mode||"hide"),l="show"===h,u=t.direction||"vertical",d="vertical"===u,c=d?"height":"width",p=d?"top":"left",f={};e.effects.save(o,r),o.show(),s=e.effects.createWrapper(o).css({overflow:"hidden"}),n="IMG"===o[0].tagName?s:o,a=n[c](),l&&(n.css(c,0),n.css(p,a/2)),f[c]=l?a:0,f[p]=l?0:a/2,n.animate(f,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){l||o.hide(),e.effects.restore(o,r),e.effects.removeWrapper(o),i()}})},e.effects.effect.drop=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","opacity","height","width"],o=e.effects.setMode(n,t.mode||"hide"),r="show"===o,h=t.direction||"left",l="up"===h||"down"===h?"top":"left",u="up"===h||"left"===h?"pos":"neg",d={opacity:r?1:0};e.effects.save(n,a),n.show(),e.effects.createWrapper(n),s=t.distance||n["top"===l?"outerHeight":"outerWidth"](!0)/2,r&&n.css("opacity",0).css(l,"pos"===u?-s:s),d[l]=(r?"pos"===u?"+=":"-=":"pos"===u?"-=":"+=")+s,n.animate(d,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}})},e.effects.effect.explode=function(t,i){function s(){b.push(this),b.length===d*c&&n()}function n(){p.css({visibility:"visible"}),e(b).remove(),m||p.hide(),i()}var a,o,r,h,l,u,d=t.pieces?Math.round(Math.sqrt(t.pieces)):3,c=d,p=e(this),f=e.effects.setMode(p,t.mode||"hide"),m="show"===f,g=p.show().css("visibility","hidden").offset(),v=Math.ceil(p.outerWidth()/c),y=Math.ceil(p.outerHeight()/d),b=[];for(a=0;d>a;a++)for(h=g.top+a*y,u=a-(d-1)/2,o=0;c>o;o++)r=g.left+o*v,l=o-(c-1)/2,p.clone().appendTo("body").wrap("
      ").css({position:"absolute",visibility:"visible",left:-o*v,top:-a*y}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:v,height:y,left:r+(m?l*v:0),top:h+(m?u*y:0),opacity:m?0:1}).animate({left:r+(m?0:l*v),top:h+(m?0:u*y),opacity:m?1:0},t.duration||500,t.easing,s)},e.effects.effect.fade=function(t,i){var s=e(this),n=e.effects.setMode(s,t.mode||"toggle");s.animate({opacity:n},{queue:!1,duration:t.duration,easing:t.easing,complete:i})},e.effects.effect.fold=function(t,i){var s,n,a=e(this),o=["position","top","bottom","left","right","height","width"],r=e.effects.setMode(a,t.mode||"hide"),h="show"===r,l="hide"===r,u=t.size||15,d=/([0-9]+)%/.exec(u),c=!!t.horizFirst,p=h!==c,f=p?["width","height"]:["height","width"],m=t.duration/2,g={},v={};e.effects.save(a,o),a.show(),s=e.effects.createWrapper(a).css({overflow:"hidden"}),n=p?[s.width(),s.height()]:[s.height(),s.width()],d&&(u=parseInt(d[1],10)/100*n[l?0:1]),h&&s.css(c?{height:0,width:u}:{height:u,width:0}),g[f[0]]=h?n[0]:u,v[f[1]]=h?n[1]:0,s.animate(g,m,t.easing).animate(v,m,t.easing,function(){l&&a.hide(),e.effects.restore(a,o),e.effects.removeWrapper(a),i()})},e.effects.effect.highlight=function(t,i){var s=e(this),n=["backgroundImage","backgroundColor","opacity"],a=e.effects.setMode(s,t.mode||"show"),o={backgroundColor:s.css("backgroundColor")};"hide"===a&&(o.opacity=0),e.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===a&&s.hide(),e.effects.restore(s,n),i()}})},e.effects.effect.size=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","width","height","overflow","opacity"],h=["position","top","bottom","left","right","overflow","opacity"],l=["width","height","overflow"],u=["fontSize"],d=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],c=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),f=t.restore||"effect"!==p,m=t.scale||"both",g=t.origin||["middle","center"],v=o.css("position"),y=f?r:h,b={height:0,width:0,outerHeight:0,outerWidth:0};"show"===p&&o.show(),s={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},"toggle"===t.mode&&"show"===p?(o.from=t.to||b,o.to=t.from||s):(o.from=t.from||("show"===p?b:s),o.to=t.to||("hide"===p?b:s)),a={from:{y:o.from.height/s.height,x:o.from.width/s.width},to:{y:o.to.height/s.height,x:o.to.width/s.width}},("box"===m||"both"===m)&&(a.from.y!==a.to.y&&(y=y.concat(d),o.from=e.effects.setTransition(o,d,a.from.y,o.from),o.to=e.effects.setTransition(o,d,a.to.y,o.to)),a.from.x!==a.to.x&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,a.from.x,o.from),o.to=e.effects.setTransition(o,c,a.to.x,o.to))),("content"===m||"both"===m)&&a.from.y!==a.to.y&&(y=y.concat(u).concat(l),o.from=e.effects.setTransition(o,u,a.from.y,o.from),o.to=e.effects.setTransition(o,u,a.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),g&&(n=e.effects.getBaseline(g,s),o.from.top=(s.outerHeight-o.outerHeight())*n.y,o.from.left=(s.outerWidth-o.outerWidth())*n.x,o.to.top=(s.outerHeight-o.to.outerHeight)*n.y,o.to.left=(s.outerWidth-o.to.outerWidth)*n.x),o.css(o.from),("content"===m||"both"===m)&&(d=d.concat(["marginTop","marginBottom"]).concat(u),c=c.concat(["marginLeft","marginRight"]),l=r.concat(d).concat(c),o.find("*[width]").each(function(){var i=e(this),s={height:i.height(),width:i.width(),outerHeight:i.outerHeight(),outerWidth:i.outerWidth()}; -f&&e.effects.save(i,l),i.from={height:s.height*a.from.y,width:s.width*a.from.x,outerHeight:s.outerHeight*a.from.y,outerWidth:s.outerWidth*a.from.x},i.to={height:s.height*a.to.y,width:s.width*a.to.x,outerHeight:s.height*a.to.y,outerWidth:s.width*a.to.x},a.from.y!==a.to.y&&(i.from=e.effects.setTransition(i,d,a.from.y,i.from),i.to=e.effects.setTransition(i,d,a.to.y,i.to)),a.from.x!==a.to.x&&(i.from=e.effects.setTransition(i,c,a.from.x,i.from),i.to=e.effects.setTransition(i,c,a.to.x,i.to)),i.css(i.from),i.animate(i.to,t.duration,t.easing,function(){f&&e.effects.restore(i,l)})})),o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){0===o.to.opacity&&o.css("opacity",o.from.opacity),"hide"===p&&o.hide(),e.effects.restore(o,y),f||("static"===v?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,i){var s=parseInt(i,10),n=e?o.to.left:o.to.top;return"auto"===i?n+"px":s+n+"px"})})),e.effects.removeWrapper(o),i()}})},e.effects.effect.scale=function(t,i){var s=e(this),n=e.extend(!0,{},t),a=e.effects.setMode(s,t.mode||"effect"),o=parseInt(t.percent,10)||(0===parseInt(t.percent,10)?0:"hide"===a?0:100),r=t.direction||"both",h=t.origin,l={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()},u={y:"horizontal"!==r?o/100:1,x:"vertical"!==r?o/100:1};n.effect="size",n.queue=!1,n.complete=i,"effect"!==a&&(n.origin=h||["middle","center"],n.restore=!0),n.from=t.from||("show"===a?{height:0,width:0,outerHeight:0,outerWidth:0}:l),n.to={height:l.height*u.y,width:l.width*u.x,outerHeight:l.outerHeight*u.y,outerWidth:l.outerWidth*u.x},n.fade&&("show"===a&&(n.from.opacity=0,n.to.opacity=1),"hide"===a&&(n.from.opacity=1,n.to.opacity=0)),s.effect(n)},e.effects.effect.puff=function(t,i){var s=e(this),n=e.effects.setMode(s,t.mode||"hide"),a="hide"===n,o=parseInt(t.percent,10)||150,r=o/100,h={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:n,complete:i,percent:a?o:100,from:a?h:{height:h.height*r,width:h.width*r,outerHeight:h.outerHeight*r,outerWidth:h.outerWidth*r}}),s.effect(t)},e.effects.effect.pulsate=function(t,i){var s,n=e(this),a=e.effects.setMode(n,t.mode||"show"),o="show"===a,r="hide"===a,h=o||"hide"===a,l=2*(t.times||5)+(h?1:0),u=t.duration/l,d=0,c=n.queue(),p=c.length;for((o||!n.is(":visible"))&&(n.css("opacity",0).show(),d=1),s=1;l>s;s++)n.animate({opacity:d},u,t.easing),d=1-d;n.animate({opacity:d},u,t.easing),n.queue(function(){r&&n.hide(),i()}),p>1&&c.splice.apply(c,[1,0].concat(c.splice(p,l+1))),n.dequeue()},e.effects.effect.shake=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","height","width"],o=e.effects.setMode(n,t.mode||"effect"),r=t.direction||"left",h=t.distance||20,l=t.times||3,u=2*l+1,d=Math.round(t.duration/u),c="up"===r||"down"===r?"top":"left",p="up"===r||"left"===r,f={},m={},g={},v=n.queue(),y=v.length;for(e.effects.save(n,a),n.show(),e.effects.createWrapper(n),f[c]=(p?"-=":"+=")+h,m[c]=(p?"+=":"-=")+2*h,g[c]=(p?"-=":"+=")+2*h,n.animate(f,d,t.easing),s=1;l>s;s++)n.animate(m,d,t.easing).animate(g,d,t.easing);n.animate(m,d,t.easing).animate(f,d/2,t.easing).queue(function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}),y>1&&v.splice.apply(v,[1,0].concat(v.splice(y,u+1))),n.dequeue()},e.effects.effect.slide=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","width","height"],o=e.effects.setMode(n,t.mode||"show"),r="show"===o,h=t.direction||"left",l="up"===h||"down"===h?"top":"left",u="up"===h||"left"===h,d={};e.effects.save(n,a),n.show(),s=t.distance||n["top"===l?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(n).css({overflow:"hidden"}),r&&n.css(l,u?isNaN(s)?"-"+s:-s:s),d[l]=(r?u?"+=":"-=":u?"-=":"+=")+s,n.animate(d,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}})},e.effects.effect.transfer=function(t,i){var s=e(this),n=e(t.to),a="fixed"===n.css("position"),o=e("body"),r=a?o.scrollTop():0,h=a?o.scrollLeft():0,l=n.offset(),u={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},d=s.offset(),c=e("
      ").appendTo(document.body).addClass(t.className).css({top:d.top-r,left:d.left-h,height:s.innerHeight(),width:s.innerWidth(),position:a?"fixed":"absolute"}).animate(u,t.duration,t.easing,function(){c.remove(),i()})},e.widget("ui.progressbar",{version:"1.11.2",options:{max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min}),this.valueDiv=e("
      ").appendTo(this.element),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return void 0===e?this.options.value:(this.options.value=this._constrainedValue(e),this._refreshValue(),void 0)},_constrainedValue:function(e){return void 0===e&&(e=this.options.value),this.indeterminate=e===!1,"number"!=typeof e&&(e=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,e))},_setOptions:function(e){var t=e.value;delete e.value,this._super(e),this.options.value=this._constrainedValue(t),this._refreshValue()},_setOption:function(e,t){"max"===e&&(t=Math.max(this.min,t)),"disabled"===e&&this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this._super(e,t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var t=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||t>this.min).toggleClass("ui-corner-right",t===this.options.max).width(i.toFixed(0)+"%"),this.element.toggleClass("ui-progressbar-indeterminate",this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=e("
      ").appendTo(this.valueDiv))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":t}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==t&&(this.oldValue=t,this._trigger("change")),t===this.options.max&&this._trigger("complete")}}),e.widget("ui.selectable",e.ui.mouse,{version:"1.11.2",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var t,i=this;this.element.addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){t=e(i.options.filter,i.element[0]),t.addClass("ui-selectee"),t.each(function(){var t=e(this),i=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:i.left,top:i.top,right:i.left+t.outerWidth(),bottom:i.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=t.addClass("ui-selectee"),this._mouseInit(),this.helper=e("
      ")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var i=this,s=this.options;this.opos=[t.pageX,t.pageY],this.options.disabled||(this.selectees=e(s.filter,this.element[0]),this._trigger("start",t),e(s.appendTo).append(this.helper),this.helper.css({left:t.pageX,top:t.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=e.data(this,"selectable-item");s.startselected=!0,t.metaKey||t.ctrlKey||(s.$element.removeClass("ui-selected"),s.selected=!1,s.$element.addClass("ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",t,{unselecting:s.element}))}),e(t.target).parents().addBack().each(function(){var s,n=e.data(this,"selectable-item");return n?(s=!t.metaKey&&!t.ctrlKey||!n.$element.hasClass("ui-selected"),n.$element.removeClass(s?"ui-unselecting":"ui-selected").addClass(s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",t,{selecting:n.element}):i._trigger("unselecting",t,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(t){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,a=this.opos[0],o=this.opos[1],r=t.pageX,h=t.pageY;return a>r&&(i=r,r=a,a=i),o>h&&(i=h,h=o,o=i),this.helper.css({left:a,top:o,width:r-a,height:h-o}),this.selectees.each(function(){var i=e.data(this,"selectable-item"),l=!1;i&&i.element!==s.element[0]&&("touch"===n.tolerance?l=!(i.left>r||a>i.right||i.top>h||o>i.bottom):"fit"===n.tolerance&&(l=i.left>a&&r>i.right&&i.top>o&&h>i.bottom),l?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,s._trigger("selecting",t,{selecting:i.element}))):(i.selecting&&((t.metaKey||t.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",t,{unselecting:i.element}))),i.selected&&(t.metaKey||t.ctrlKey||i.startselected||(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",t,{unselecting:i.element})))))}),!1}},_mouseStop:function(t){var i=this;return this.dragged=!1,e(".ui-unselecting",this.element[0]).each(function(){var s=e.data(this,"selectable-item");s.$element.removeClass("ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",t,{unselected:s.element})}),e(".ui-selecting",this.element[0]).each(function(){var s=e.data(this,"selectable-item");s.$element.removeClass("ui-selecting").addClass("ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",t,{selected:s.element})}),this._trigger("stop",t),this.helper.remove(),!1}}),e.widget("ui.selectmenu",{version:"1.11.2",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},i=this.element;return e.each(["min","max","step"],function(e,s){var n=i.attr(s);void 0!==n&&n.length&&(t[s]=n)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e),void 0)},mousewheel:function(e,t){if(t){if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()}},"mousedown .ui-spinner-button":function(t){function i(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(t)!==!1&&this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){return e(t.currentTarget).hasClass("ui-state-active")?this._start(t)===!1?!1:(this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(.5*e.height())&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var i=this.options,s=e.ui.keyCode;switch(t.keyCode){case s.UP:return this._repeat(null,1,t),!0;case s.DOWN:return this._repeat(null,-1,t),!0;case s.PAGE_UP:return this._repeat(null,i.page,t),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,t),!0}return!1},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""+""+""+""+""},_start:function(e){return this.spinning||this._trigger("start",e)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(e,t,i){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,i)},e),this._spin(t*this.options.step,i)},_spin:function(e,t){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+e*this._increment(this.counter)),this.spinning&&this._trigger("spin",t,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(t){var i=this.options.incremental;return i?e.isFunction(i)?i(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return null!==this.options.min&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=""+e,i=t.indexOf(".");return-1===i?0:t.length-i-1},_adjustValue:function(e){var t,i,s=this.options;return t=null!==s.min?s.min:0,i=e-t,i=Math.round(i/s.step)*s.step,e=t+i,e=parseFloat(e.toFixed(this._precision())),null!==s.max&&e>s.max?s.max:null!==s.min&&s.min>e?s.min:e},_stop:function(e){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",e))},_setOption:function(e,t){if("culture"===e||"numberFormat"===e){var i=this._parse(this.element.val());return this.options[e]=t,this.element.val(this._format(i)),void 0}("max"===e||"min"===e||"step"===e)&&"string"==typeof t&&(t=this._parse(t)),"icons"===e&&(this.buttons.first().find(".ui-icon").removeClass(this.options.icons.up).addClass(t.up),this.buttons.last().find(".ui-icon").removeClass(this.options.icons.down).addClass(t.down)),this._super(e,t),"disabled"===e&&(this.widget().toggleClass("ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable"))},_setOptions:h(function(e){this._super(e)}),_parse:function(e){return"string"==typeof e&&""!==e&&(e=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(e,10,this.options.culture):+e),""===e||isNaN(e)?null:e},_format:function(e){return""===e?"":window.Globalize&&this.options.numberFormat?Globalize.format(e,this.options.numberFormat,this.options.culture):e},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var e=this.value();return null===e?!1:e===this._adjustValue(e)},_value:function(e,t){var i;""!==e&&(i=this._parse(e),null!==i&&(t||(i=this._adjustValue(i)),e=this._format(i))),this.element.val(e),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:h(function(e){this._stepUp(e)}),_stepUp:function(e){this._start()&&(this._spin((e||1)*this.options.step),this._stop())},stepDown:h(function(e){this._stepDown(e)}),_stepDown:function(e){this._start()&&(this._spin((e||1)*-this.options.step),this._stop())},pageUp:h(function(e){this._stepUp((e||1)*this.options.page)}),pageDown:h(function(e){this._stepDown((e||1)*this.options.page)}),value:function(e){return arguments.length?(h(this._value).call(this,e),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),e.widget("ui.tabs",{version:"1.11.2",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var e=/#.*$/;return function(t){var i,s;t=t.cloneNode(!1),i=t.href.replace(e,""),s=location.href.replace(e,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return t.hash.length>1&&i===s}}(),_create:function(){var t=this,i=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",i.collapsible),this._processTabs(),i.active=this._initialActive(),e.isArray(i.disabled)&&(i.disabled=e.unique(i.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):e(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var t=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===t&&(s&&this.tabs.each(function(i,n){return e(n).attr("aria-controls")===s?(t=i,!1):void 0}),null===t&&(t=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===t||-1===t)&&(t=this.tabs.length?0:!1)),t!==!1&&(t=this.tabs.index(this.tabs.eq(t)),-1===t&&(t=i?!1:0)),!i&&t===!1&&this.anchors.length&&(t=0),t},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var i=e(this.document[0].activeElement).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(t)){switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:s++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:n=!1,s--;break;case e.ui.keyCode.END:s=this.anchors.length-1;break;case e.ui.keyCode.HOME:s=0;break;case e.ui.keyCode.SPACE:return t.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case e.ui.keyCode.ENTER:return t.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}t.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),t.ctrlKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(t){this._handlePageNav(t)||t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){return t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(t,i){function s(){return t>n&&(t=0),0>t&&(t=n),t}for(var n=this.tabs.length-1;-1!==e.inArray(s(),this.options.disabled);)t=i?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):"disabled"===e?(this._setupDisabled(t),void 0):(this._super(e,t),"collapsible"===e&&(this.element.toggleClass("ui-tabs-collapsible",t),t||this.options.active!==!1||this._activate(0)),"event"===e&&this._setupEvents(t),"heightStyle"===e&&this._setupHeightStyle(t),void 0)},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,i=this.tablist.children(":has(a[href])");t.disabled=e.map(i.filter(".ui-state-disabled"),function(e){return i.index(e)}),this._processTabs(),t.active!==!1&&this.anchors.length?this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active):(t.active=!1,this.active=e()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this,i=this.tabs,s=this.anchors,n=this.panels;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist").delegate("> li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0] -}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(i,s){var n,a,o,r=e(s).uniqueId().attr("id"),h=e(s).closest("li"),l=h.attr("aria-controls");t._isLocal(s)?(n=s.hash,o=n.substring(1),a=t.element.find(t._sanitizeSelector(n))):(o=h.attr("aria-controls")||e({}).uniqueId()[0].id,n="#"+o,a=t.element.find(n),a.length||(a=t._createPanel(o),a.insertAfter(t.panels[i-1]||t.tablist)),a.attr("aria-live","polite")),a.length&&(t.panels=t.panels.add(a)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":o,"aria-labelledby":r}),a.attr("aria-labelledby",r)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel"),i&&(this._off(i.not(this.tabs)),this._off(s.not(this.anchors)),this._off(n.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("
      ").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var i,s=0;i=this.tabs[s];s++)t===!0||-1!==e.inArray(s,t)?e(i).addClass("ui-state-disabled").attr("aria-disabled","true"):e(i).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var i={};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(e){e.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var i,s=this.element.parent();"fill"===t?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var t=e(this),s=t.css("position");"absolute"!==s&&"fixed"!==s&&(i-=t.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,i-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===t&&(i=0,this.panels.each(function(){i=Math.max(i,e(this).height("").height())}).height(i))},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n.closest("li"),o=a[0]===s[0],r=o&&i.collapsible,h=r?e():this._getPanelForTab(a),l=s.length?this._getPanelForTab(s):e(),u={oldTab:s,oldPanel:l,newTab:r?e():a,newPanel:h};t.preventDefault(),a.hasClass("ui-state-disabled")||a.hasClass("ui-tabs-loading")||this.running||o&&!i.collapsible||this._trigger("beforeActivate",t,u)===!1||(i.active=r?!1:this.tabs.index(a),this.active=o?e():a,this.xhr&&this.xhr.abort(),l.length||h.length||e.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(a),t),this._toggle(t,u))},_toggle:function(t,i){function s(){a.running=!1,a._trigger("activate",t,i)}function n(){i.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),o.length&&a.options.show?a._show(o,a.options.show,s):(o.show(),s())}var a=this,o=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),n()}):(i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),o.length&&r.length?i.oldTab.attr("tabIndex",-1):o.length&&this.tabs.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),o.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(t){var i,s=this._findActive(t);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tablist.unbind(this.eventNamespace),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),i=t.data("ui-tabs-aria-controls");i?t.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):t.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(t){var i=this.options.disabled;i!==!1&&(void 0===t?i=!1:(t=this._getIndex(t),i=e.isArray(i)?e.map(i,function(e){return e!==t?e:null}):e.map(this.tabs,function(e,i){return i!==t?i:null})),this._setupDisabled(i))},disable:function(t){var i=this.options.disabled;if(i!==!0){if(void 0===t)i=!0;else{if(t=this._getIndex(t),-1!==e.inArray(t,i))return;i=e.isArray(i)?e.merge([t],i).sort():[t]}this._setupDisabled(i)}},load:function(t,i){t=this._getIndex(t);var s=this,n=this.tabs.eq(t),a=n.find(".ui-tabs-anchor"),o=this._getPanelForTab(n),r={tab:n,panel:o};this._isLocal(a[0])||(this.xhr=e.ajax(this._ajaxSettings(a,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(n.addClass("ui-tabs-loading"),o.attr("aria-busy","true"),this.xhr.success(function(e){setTimeout(function(){o.html(e),s._trigger("load",i,r)},1)}).complete(function(e,t){setTimeout(function(){"abort"===t&&s.panels.stop(!1,!0),n.removeClass("ui-tabs-loading"),o.removeAttr("aria-busy"),e===s.xhr&&delete s.xhr},1)})))},_ajaxSettings:function(t,i,s){var n=this;return{url:t.attr("href"),beforeSend:function(t,a){return n._trigger("beforeLoad",i,e.extend({jqXHR:t,ajaxSettings:a},s))}}},_getPanelForTab:function(t){var i=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),e.widget("ui.tooltip",{version:"1.11.2",options:{content:function(){var t=e(this).attr("title")||"";return e("").text(t).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_addDescribedBy:function(t,i){var s=(t.attr("aria-describedby")||"").split(/\s+/);s.push(i),t.data("ui-tooltip-id",i).attr("aria-describedby",e.trim(s.join(" ")))},_removeDescribedBy:function(t){var i=t.data("ui-tooltip-id"),s=(t.attr("aria-describedby")||"").split(/\s+/),n=e.inArray(i,s);-1!==n&&s.splice(n,1),t.removeData("ui-tooltip-id"),s=e.trim(s.join(" ")),s?t.attr("aria-describedby",s):t.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable(),this.liveRegion=e("
      ").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).addClass("ui-helper-hidden-accessible").appendTo(this.document[0].body)},_setOption:function(t,i){var s=this;return"disabled"===t?(this[i?"_disable":"_enable"](),this.options[t]=i,void 0):(this._super(t,i),"content"===t&&e.each(this.tooltips,function(e,t){s._updateContent(t.element)}),void 0)},_disable:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur");n.target=n.currentTarget=s.element[0],t.close(n,!0)}),this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).removeAttr("title")})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var i=this,s=e(t?t.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),t&&"mouseover"===t.type&&s.parents().each(function(){var t,s=e(this);s.data("ui-tooltip-open")&&(t=e.Event("blur"),t.target=t.currentTarget=this,i.close(t,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._updateContent(s,t))},_updateContent:function(e,t){var i,s=this.options.content,n=this,a=t?t.type:null;return"string"==typeof s?this._open(t,e,s):(i=s.call(e[0],function(i){e.data("ui-tooltip-open")&&n._delay(function(){t&&(t.type=a),this._open(t,e,i)})}),i&&this._open(t,e,i),void 0)},_open:function(t,i,s){function n(e){u.of=e,o.is(":hidden")||o.position(u)}var a,o,r,h,l,u=e.extend({},this.options.position);if(s){if(a=this._find(i))return a.tooltip.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(t&&"mouseover"===t.type?i.attr("title",""):i.removeAttr("title")),a=this._tooltip(i),o=a.tooltip,this._addDescribedBy(i,o.attr("id")),o.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),s.clone?(l=s.clone(),l.removeAttr("id").find("[id]").removeAttr("id")):l=s,e("
      ").html(l).appendTo(this.liveRegion),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:n}),n(t)):o.position(e.extend({of:i},this.options.position)),o.hide(),this._show(o,this.options.show),this.options.show&&this.options.show.delay&&(h=this.delayedShow=setInterval(function(){o.is(":visible")&&(n(u.of),clearInterval(h))},e.fx.interval)),this._trigger("open",t,{tooltip:o}),r={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var s=e.Event(t);s.currentTarget=i[0],this.close(s,!0)}}},i[0]!==this.element[0]&&(r.remove=function(){this._removeTooltip(o)}),t&&"mouseover"!==t.type||(r.mouseleave="close"),t&&"focusin"!==t.type||(r.focusout="close"),this._on(!0,i,r)}},close:function(t){var i,s=this,n=e(t?t.currentTarget:this.element),a=this._find(n);a&&(i=a.tooltip,a.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&!n.attr("title")&&n.attr("title",n.data("ui-tooltip-title")),this._removeDescribedBy(n),a.hiding=!0,i.stop(!0),this._hide(i,this.options.hide,function(){s._removeTooltip(e(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),t&&"mouseleave"===t.type&&e.each(this.parents,function(t,i){e(i.element).attr("title",i.title),delete s.parents[t]}),a.closing=!0,this._trigger("close",t,{tooltip:i}),a.hiding||(a.closing=!1)))},_tooltip:function(t){var i=e("
      ").attr("role","tooltip").addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||"")),s=i.uniqueId().attr("id");return e("
      ").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),this.tooltips[s]={element:t,tooltip:i}},_find:function(e){var t=e.data("ui-tooltip-id");return t?this.tooltips[t]:null},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur"),a=s.element;n.target=n.currentTarget=a[0],t.close(n,!0),e("#"+i).remove(),a.data("ui-tooltip-title")&&(a.attr("title")||a.attr("title",a.data("ui-tooltip-title")),a.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}})}); \ No newline at end of file diff --git a/ext/autocomplete/lib/jquery-ui.theme.min.css b/ext/autocomplete/lib/jquery-ui.theme.min.css deleted file mode 100644 index 1b9f9bfa58..0000000000 --- a/ext/autocomplete/lib/jquery-ui.theme.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! jQuery UI - v1.11.2 - 2015-01-31 -* http://jqueryui.com -* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ - -.ui-widget{font-family:Helvetica,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Helvetica,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#fff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x;color:#444}.ui-widget-content a{color:#444}.ui-widget-header{border:1px solid #ddd;background:#ddd url("images/ui-bg_highlight-soft_50_dddddd_1x100.png") 50% 50% repeat-x;color:#444;font-weight:bold}.ui-widget-header a{color:#444}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ddd;background:#f6f6f6 url("images/ui-bg_highlight-soft_100_f6f6f6_1x100.png") 50% 50% repeat-x;font-weight:bold;color:#0073ea}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#0073ea;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #0073ea;background:#0073ea url("images/ui-bg_highlight-soft_25_0073ea_1x100.png") 50% 50% repeat-x;font-weight:bold;color:#fff}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#fff;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #ddd;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#ff0084}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#ff0084;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #ccc;background:#fff url("images/ui-bg_flat_55_ffffff_40x100.png") 50% 50% repeat-x;color:#444}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#444}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #ff0084;background:#fff url("images/ui-bg_flat_55_ffffff_40x100.png") 50% 50% repeat-x;color:#222}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#222}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#222}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_ff0084_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_0073ea_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_666666_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_0073ea_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_ff0084_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:2px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:2px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:2px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:2px}.ui-widget-overlay{background:#eee url("images/ui-bg_flat_0_eeeeee_40x100.png") 50% 50% repeat-x;opacity:.8;filter:Alpha(Opacity=80)}.ui-widget-shadow{margin:-4px 0 0 -4px;padding:4px;background:#aaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.6;filter:Alpha(Opacity=60);border-radius:0} \ No newline at end of file diff --git a/ext/autocomplete/lib/jquery.tagit.css b/ext/autocomplete/lib/jquery.tagit.css deleted file mode 100644 index f18650d91a..0000000000 --- a/ext/autocomplete/lib/jquery.tagit.css +++ /dev/null @@ -1,69 +0,0 @@ -ul.tagit { - padding: 1px 5px; - overflow: auto; - margin-left: inherit; /* usually we don't want the regular ul margins. */ - margin-right: inherit; -} -ul.tagit li { - display: block; - float: left; - margin: 2px 5px 2px 0; -} -ul.tagit li.tagit-choice { - position: relative; - line-height: inherit; -} -input.tagit-hidden-field { - display: none; -} -ul.tagit li.tagit-choice-read-only { - padding: .2em .5em .2em .5em; -} - -ul.tagit li.tagit-choice-editable { - padding: .2em 18px .2em .5em; -} - -ul.tagit li.tagit-new { - padding: .25em 4px .25em 0; -} - -ul.tagit li.tagit-choice a.tagit-label { - cursor: pointer; - text-decoration: none; -} -ul.tagit li.tagit-choice .tagit-close { - cursor: pointer; - position: absolute; - right: .1em; - top: 50%; - margin-top: -8px; - line-height: 17px; -} - -/* used for some custom themes that don't need image icons */ -ul.tagit li.tagit-choice .tagit-close .text-icon { - display: none; -} - -ul.tagit li.tagit-choice input { - display: block; - float: left; - margin: 2px 5px 2px 0; -} -ul.tagit input[type="text"] { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; - - border: none; - margin: 0; - padding: 0; - width: inherit; - background-color: inherit; - outline: none; -} diff --git a/ext/autocomplete/lib/tag-it.min.js b/ext/autocomplete/lib/tag-it.min.js deleted file mode 100644 index af48aee1a7..0000000000 --- a/ext/autocomplete/lib/tag-it.min.js +++ /dev/null @@ -1,18 +0,0 @@ -//Removed TAB keybind -;(function(b){b.widget("ui.tagit",{options:{allowDuplicates:!1,caseSensitive:!0,fieldName:"tags",placeholderText:null,readOnly:!1,removeConfirmation:!1,tagLimit:null,availableTags:[],autocomplete:{},showAutocompleteOnFocus:!1,allowSpaces:!1,singleField:!1,singleFieldDelimiter:",",singleFieldNode:null,animate:!0,tabIndex:null,beforeTagAdded:null,afterTagAdded:null,beforeTagRemoved:null,afterTagRemoved:null,onTagClicked:null,onTagLimitExceeded:null,onTagAdded:null,onTagRemoved:null,tagSource:null},_create:function(){var a= -this;this.element.is("input")?(this.tagList=b("
        ").insertAfter(this.element),this.options.singleField=!0,this.options.singleFieldNode=this.element,this.element.addClass("tagit-hidden-field")):this.tagList=this.element.find("ul, ol").andSelf().last();this.tagInput=b('').addClass("ui-widget-content");this.options.readOnly&&this.tagInput.attr("disabled","disabled");this.options.tabIndex&&this.tagInput.attr("tabindex",this.options.tabIndex);this.options.placeholderText&&this.tagInput.attr("placeholder", -this.options.placeholderText);this.options.autocomplete.source||(this.options.autocomplete.source=function(a,e){var d=a.term.toLowerCase(),c=b.grep(this.options.availableTags,function(a){return 0===a.toLowerCase().indexOf(d)});this.options.allowDuplicates||(c=this._subtractArray(c,this.assignedTags()));e(c)});this.options.showAutocompleteOnFocus&&(this.tagInput.focus(function(b,d){a._showAutocomplete()}),"undefined"===typeof this.options.autocomplete.minLength&&(this.options.autocomplete.minLength= -0));b.isFunction(this.options.autocomplete.source)&&(this.options.autocomplete.source=b.proxy(this.options.autocomplete.source,this));b.isFunction(this.options.tagSource)&&(this.options.tagSource=b.proxy(this.options.tagSource,this));this.tagList.addClass("tagit").addClass("ui-widget ui-widget-content ui-corner-all").append(b('
      • ').append(this.tagInput)).click(function(d){var c=b(d.target);c.hasClass("tagit-label")?(c=c.closest(".tagit-choice"),c.hasClass("removed")||a._trigger("onTagClicked", -d,{tag:c,tagLabel:a.tagLabel(c)})):a.tagInput.focus()});var c=!1;if(this.options.singleField)if(this.options.singleFieldNode){var d=b(this.options.singleFieldNode),f=d.val().split(this.options.singleFieldDelimiter);d.val("");b.each(f,function(b,d){a.createTag(d,null,!0);c=!0})}else this.options.singleFieldNode=b(''),this.tagList.after(this.options.singleFieldNode);c||this.tagList.children("li").each(function(){b(this).hasClass("tagit-new")|| -(a.createTag(b(this).text(),b(this).attr("class"),!0),b(this).remove())});this.tagInput.keydown(function(c){if(c.which==b.ui.keyCode.BACKSPACE&&""===a.tagInput.val()){var d=a._lastTag();!a.options.removeConfirmation||d.hasClass("remove")?a.removeTag(d):a.options.removeConfirmation&&d.addClass("remove ui-state-highlight")}else a.options.removeConfirmation&&a._lastTag().removeClass("remove ui-state-highlight");if(c.which===b.ui.keyCode.COMMA&&!1===c.shiftKey||c.which===b.ui.keyCode.ENTER||c.which== -c.which==b.ui.keyCode.SPACE&&!0!==a.options.allowSpaces&&('"'!=b.trim(a.tagInput.val()).replace(/^s*/,"").charAt(0)||'"'==b.trim(a.tagInput.val()).charAt(0)&&'"'==b.trim(a.tagInput.val()).charAt(b.trim(a.tagInput.val()).length-1)&&0!==b.trim(a.tagInput.val()).length-1))c.which===b.ui.keyCode.ENTER&&""===a.tagInput.val()||c.preventDefault(),a.options.autocomplete.autoFocus&&a.tagInput.data("autocomplete-open")||(a.tagInput.autocomplete("close"),a.createTag(a._cleanedInput()))}).blur(function(b){a.tagInput.data("autocomplete-open")|| -a.createTag(a._cleanedInput())});if(this.options.availableTags||this.options.tagSource||this.options.autocomplete.source)d={select:function(b,c){a.createTag(c.item.value);return!1}},b.extend(d,this.options.autocomplete),d.source=this.options.tagSource||d.source,this.tagInput.autocomplete(d).bind("autocompleteopen.tagit",function(b,c){a.tagInput.data("autocomplete-open",!0)}).bind("autocompleteclose.tagit",function(b,c){a.tagInput.data("autocomplete-open",!1)}),this.tagInput.autocomplete("widget").addClass("tagit-autocomplete")}, -destroy:function(){b.Widget.prototype.destroy.call(this);this.element.unbind(".tagit");this.tagList.unbind(".tagit");this.tagInput.removeData("autocomplete-open");this.tagList.removeClass("tagit ui-widget ui-widget-content ui-corner-all tagit-hidden-field");this.element.is("input")?(this.element.removeClass("tagit-hidden-field"),this.tagList.remove()):(this.element.children("li").each(function(){b(this).hasClass("tagit-new")?b(this).remove():(b(this).removeClass("tagit-choice ui-widget-content ui-state-default ui-state-highlight ui-corner-all remove tagit-choice-editable tagit-choice-read-only"), -b(this).text(b(this).children(".tagit-label").text()))}),this.singleFieldNode&&this.singleFieldNode.remove());return this},_cleanedInput:function(){return b.trim(this.tagInput.val().replace(/^"(.*)"$/,"$1"))},_lastTag:function(){return this.tagList.find(".tagit-choice:last:not(.removed)")},_tags:function(){return this.tagList.find(".tagit-choice:not(.removed)")},assignedTags:function(){var a=this,c=[];this.options.singleField?(c=b(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter), -""===c[0]&&(c=[])):this._tags().each(function(){c.push(a.tagLabel(this))});return c},_updateSingleTagsField:function(a){b(this.options.singleFieldNode).val(a.join(this.options.singleFieldDelimiter)).trigger("change")},_subtractArray:function(a,c){for(var d=[],f=0;f=this.options.tagLimit)return this._trigger("onTagLimitExceeded",null,{duringInitialization:d}),!1;var g=b(this.options.onTagClicked?'
        ':'').text(a),e=b("
      • ").addClass("tagit-choice ui-widget-content ui-state-default ui-corner-all").addClass(c).append(g); -this.options.readOnly?e.addClass("tagit-choice-read-only"):(e.addClass("tagit-choice-editable"),c=b("").addClass("ui-icon ui-icon-close"),c=b('\u00d7').addClass("tagit-close").append(c).click(function(a){f.removeTag(e)}),e.append(c));this.options.singleField||(g=g.html(),e.append(''));!1!==this._trigger("beforeTagAdded",null,{tag:e,tagLabel:this.tagLabel(e), -duringInitialization:d})&&(this.options.singleField&&(g=this.assignedTags(),g.push(a),this._updateSingleTagsField(g)),this._trigger("onTagAdded",null,e),this.tagInput.val(""),this.tagInput.parent().before(e),this._trigger("afterTagAdded",null,{tag:e,tagLabel:this.tagLabel(e),duringInitialization:d}),this.options.showAutocompleteOnFocus&&!d&&setTimeout(function(){f._showAutocomplete()},0))},removeTag:function(a,c){c="undefined"===typeof c?this.options.animate:c;a=b(a);this._trigger("onTagRemoved", -null,a);if(!1!==this._trigger("beforeTagRemoved",null,{tag:a,tagLabel:this.tagLabel(a)})){if(this.options.singleField){var d=this.assignedTags(),f=this.tagLabel(a),d=b.grep(d,function(a){return a!=f});this._updateSingleTagsField(d)}if(c){a.addClass("removed");var d=this._effectExists("blind")?["blind",{direction:"horizontal"},"fast"]:["fast"],g=this;d.push(function(){a.remove();g._trigger("afterTagRemoved",null,{tag:a,tagLabel:g.tagLabel(a)})});a.fadeOut("fast").hide.apply(a,d).dequeue()}else a.remove(), -this._trigger("afterTagRemoved",null,{tag:a,tagLabel:this.tagLabel(a)})}},removeTagByLabel:function(a,b){var d=this._findTagByLabel(a);if(!d)throw"No such tag exists with the name '"+a+"'";this.removeTag(d,b)},removeAll:function(){var a=this;this._tags().each(function(b,d){a.removeTag(d,!1)})}})})(jQuery); diff --git a/ext/autocomplete/lib/tagit.ui-zendesk.css b/ext/autocomplete/lib/tagit.ui-zendesk.css deleted file mode 100644 index 18982864b5..0000000000 --- a/ext/autocomplete/lib/tagit.ui-zendesk.css +++ /dev/null @@ -1,97 +0,0 @@ - -/* Optional scoped theme for tag-it which mimics the zendesk widget. */ - - -ul.tagit { - border-style: solid; - border-width: 1px; - border-color: #C6C6C6; - background: inherit; -} -ul.tagit li.tagit-choice { - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-border-radius: 6px; - border: 1px solid #CAD8F3; - - background: #DEE7F8 none; - - font-weight: normal; -} -ul.tagit li.tagit-choice .tagit-label:not(a) { - color: #555; -} -ul.tagit li.tagit-choice a.tagit-close { - text-decoration: none; -} -ul.tagit li.tagit-choice .tagit-close { - right: .4em; -} -ul.tagit li.tagit-choice .ui-icon { - display: none; -} -ul.tagit li.tagit-choice .tagit-close .text-icon { - display: inline; - font-family: arial, sans-serif; - font-size: 16px; - line-height: 16px; - color: #777; -} -ul.tagit li.tagit-choice:hover, ul.tagit li.tagit-choice.remove { - background-color: #bbcef1; - border-color: #6d95e0; -} -ul.tagit li.tagit-choice a.tagLabel:hover, -ul.tagit li.tagit-choice a.tagit-close .text-icon:hover { - color: #222; -} -ul.tagit input[type="text"] { - color: #333333; - background: none; -} -.ui-widget { - font-size: 1.1em; -} - -/* Forked from a jQuery UI theme, so that we don't require the jQuery UI CSS as a dependency. */ -.tagit-autocomplete.ui-autocomplete { position: absolute; cursor: default; } -* html .tagit-autocomplete.ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ -.tagit-autocomplete.ui-menu { - list-style:none; - padding: 2px; - margin: 0; - display:block; - float: left; -} -.tagit-autocomplete.ui-menu .ui-menu { - margin-top: -3px; -} -.tagit-autocomplete.ui-menu .ui-menu-item { - margin:0; - padding: 0; - zoom: 1; - float: left; - clear: left; - width: 100%; -} -.tagit-autocomplete.ui-menu .ui-menu-item a { - text-decoration:none; - display:block; - padding:.2em .4em; - line-height:1.5; - zoom:1; -} -.tagit-autocomplete .ui-menu .ui-menu-item a.ui-state-hover, -.tagit-autocomplete .ui-menu .ui-menu-item a.ui-state-active { - font-weight: normal; - margin: -1px; -} -.tagit-autocomplete.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff 50% 50% repeat-x; color: #222222; } -.tagit-autocomplete.ui-corner-all, .tagit-autocomplete .ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; -khtml-border-radius: 4px; border-radius: 4px; } -.tagit-autocomplete .ui-state-hover, .tagit-autocomplete .ui-state-focus { border: 1px solid #999999; background: #dadada; font-weight: normal; color: #212121; } -.tagit-autocomplete .ui-state-active { border: 1px solid #aaaaaa; } - -.tagit-autocomplete .ui-widget-content { border: 1px solid #aaaaaa; } -.tagit .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px,1px,1px,1px); } - - diff --git a/ext/autocomplete/main.php b/ext/autocomplete/main.php index 147260e3d0..e165d30bd6 100644 --- a/ext/autocomplete/main.php +++ b/ext/autocomplete/main.php @@ -6,9 +6,6 @@ class AutoComplete extends Extension { - /** @var AutoCompleteTheme */ - protected Themelet $theme; - public function get_priority(): int { return 30; @@ -28,8 +25,6 @@ public function onPageRequest(PageRequestEvent $event) $page->set_mime(MimeType::JSON); $page->set_data(json_encode($res)); } - - $this->theme->build_autocomplete($page); } private function complete(string $search, int $limit): array diff --git a/ext/autocomplete/script.js b/ext/autocomplete/script.js index d437bbf9db..ad0859dd46 100644 --- a/ext/autocomplete/script.js +++ b/ext/autocomplete/script.js @@ -1,115 +1,211 @@ -document.addEventListener('DOMContentLoaded', () => { - var metatags = ['order:id', 'order:width', 'order:height', 'order:filesize', 'order:filename', 'order:favorites']; - - $('.autocomplete_tags').tagit({ - singleFieldDelimiter: ' ', - beforeTagAdded: function(event, ui) { - if(metatags.indexOf(ui.tagLabel) !== -1) { - ui.tag.addClass('tag-metatag'); - } else { - console.log(ui.tagLabel); - // give special class to negative tags - if(ui.tagLabel[0] === '-') { - ui.tag.addClass('tag-negative'); - }else{ - ui.tag.addClass('tag-positive'); - } - } - }, - autocomplete : ({ - source: function (request, response) { - var ac_metatags = $.map( - $.grep(metatags, function(s) { - // Only show metatags for strings longer than one character - return (request.term.length > 1 && s.indexOf(request.term) === 0); - }), - function(item) { - return { - label : item + ' [metatag]', - value : item - }; - } - ); - - var isNegative = (request.term[0] === '-'); - $.ajax({ - url: base_href + '/api/internal/autocomplete', - data: {'s': (isNegative ? request.term.substring(1) : request.term)}, - dataType : 'json', - type : 'GET', - success : function (data) { - response( - $.merge(ac_metatags, - $.map(data, function (count, item) { - item = (isNegative ? '-'+item : item); - return { - label : item + ' ('+count+')', - value : item - }; - }) - ) - ); - }, - error : function (request, status, error) { - console.log(error); - } - }); - }, - minLength: 1 - }) - }); +let completions_el = document.createElement('ul'); +completions_el.className = 'autocomplete_completions'; +completions_el.id = 'completions'; - $('#tag_editor,[name="bulk_tags"]').tagit({ - singleFieldDelimiter: ' ', - autocomplete : ({ - source: function (request, response) { - $.ajax({ - url: base_href + '/api/internal/autocomplete', - data: {'s': request.term}, - dataType : 'json', - type : 'GET', - success : function (data) { - response( - $.map(data, function (count, item) { - return { - label : item + ' ('+count+')', - value : item - }; - }) - ); - }, - error : function (request, status, error) { - console.log(error); - } - }); - }, - minLength: 1 - }) - }); +/** + * Whenever input changes, look at what word is currently + * being typed, and fetch completions for it. + * + * @param {HTMLInputElement} element + */ +function updateCompletions(element) { + highlightCompletion(element, -1); - $('.ui-autocomplete-input').keydown(function(e) { - var keyCode = e.keyCode || e.which; + let text = element.value; + let pos = element.selectionStart; - //Stop tags containing space. - if(keyCode === 32) { - e.preventDefault(); - var el = $('.ui-widget-content:focus'); + // get the word before the cursor + var start = text.lastIndexOf(' ', pos-1); + if(start === -1) { + start = 0; + } + else { + start++; // skip the space + } + var word = text.substring(start, pos); - //Find the correct element in a page with multiple tagit input boxes. - $('.autocomplete_tags').each(function(_,n){ - if (n.parentNode.contains(el[0])){ - $(n.parentNode).find('.autocomplete_tags').tagit('createTag', el.val()); + // search for completions + if(element.completer_timeout !== null) { + clearTimeout(element.completer_timeout); + element.completer_timeout = null; + } + if(word === '') { + element.completions = {}; + renderCompletions(element); + } + else { + element.completer_timeout = setTimeout(() => { + fetch(base_href + '/api/internal/autocomplete?s=' + word).then( + (response) => response.json() + ).then((json) => { + if(element.selected_completion !== -1) { + return; // user has started to navigate the completions, so don't update them } + element.completions = json; + renderCompletions(element); }); - $(this).autocomplete('close'); - } else if (keyCode === 9) { - e.preventDefault(); - - var tag = $('.tagit-autocomplete[style*=\"display: block\"] > li:focus, .tagit-autocomplete[style*=\"display: block\"] > li:first').first(); - if(tag.length){ - $(tag).click(); - $('.ui-autocomplete-input').val(''); //If tag already exists, make sure to remove duplicate. - } + }, 250); + renderCompletions(element); + } +} + +/** + * Highlight the nth completion + * + * @param {HTMLInputElement} element + * @param {number} n + */ +function highlightCompletion(element, n) { + if(!element.completions) return; + element.selected_completion = Math.min( + Math.max(n, -1), + Object.keys(element.completions).length-1 + ); + renderCompletions(element); +} + +/** + * Render the completion block + * + * @param {HTMLInputElement} element + */ +function renderCompletions(element) { + let completions = element.completions; + let selected_completion = element.selected_completion; + + // if there are no completions, remove the completion block + if(Object.keys(completions).length === 0) { + completions_el.remove(); + return; + } + + // remove all children + while(completions_el.firstChild) { + completions_el.removeChild(completions_el.firstChild); + } + + // add children for each completion, with the selected one highlighted + Object.keys(completions).forEach((key, i) => { + let value = completions[key]; + + let li = document.createElement('li'); + li.innerHTML = key + ' (' + value + ')'; + if(i === selected_completion) { + li.className = 'selected'; } + // on hover, select the completion + li.addEventListener('mouseover', () => { + highlightCompletion(element, i); + }); + // on click, set the completion + // (mousedown is used instead of click because click is + // fired after blur, which causes the completion block to + // be removed before the click event is handled) + li.addEventListener('mousedown', () => { + setCompletion(element, key); + }); + completions_el.appendChild(li); + }); + + // insert the completion block after the element + if(element.parentNode) { + element.parentNode.insertBefore(completions_el, element.nextSibling); + completions_el.style.width = element.clientWidth + 'px'; + } +} +/** + * Set the current word to the given completion + * + * @param {HTMLInputElement} element + * @param {string} new_word + */ +function setCompletion(element, new_word) { + let text = element.value; + let pos = element.selectionStart; + + // get the word before the cursor + var start = text.lastIndexOf(' ', pos-1); + if(start === -1) { + start = 0; + } + else { + start++; // skip the space + } + var end = text.indexOf(' ', pos); + if(end === -1) { + end = text.length; + } + + // replace the word with the completion + new_word += ' '; + element.value = text.substring(0, start) + new_word + text.substring(end); + element.selectionStart = start + new_word.length; + element.selectionEnd = start + new_word.length; + + // reset metadata + element.completions = {}; + element.selected_completion = -1; + if(element.completer_timeout) { + clearTimeout(element.completer_timeout); + element.completer_timeout = null; + } +} + +document.addEventListener('DOMContentLoaded', () => { + // Find all elements with class 'autocomplete_tags' + document.querySelectorAll('.autocomplete_tags').forEach((element) => { + // set metadata + element.completions = {}; + element.selected_completion = -1; + element.completer_timeout = null; + + // disable built-in autocomplete + element.setAttribute('autocomplete', 'off'); + + // when element is focused, add completion block + element.addEventListener('focus', () => { + updateCompletions(element); + }); + + // when element is blurred, remove completion block + element.addEventListener('blur', () => { + // if we are blurring because we are clicking on a completion, + // don't remove the completion block until the click event is done + completions_el.remove(); + }); + + // when cursor is moved, change current completion + document.addEventListener('selectionchange', () => { + // if element is focused + if(document.activeElement === element) { + updateCompletions(element); + } + }); + + element.addEventListener('keydown', (event) => { + // up / down should select previous / next completion + if(event.code === "ArrowUp") { + event.preventDefault(); + highlightCompletion(element, element.selected_completion-1); + } + if(event.code === "ArrowDown") { + event.preventDefault(); + highlightCompletion(element, element.selected_completion+1); + } + // if enter is pressed, add the selected completion + if(event.code === "Enter" && element.selected_completion !== -1) { + event.preventDefault(); + setCompletion(element, Object.keys(element.completions)[element.selected_completion]); + } + // if escape is pressed, hide the completion block + if(event.code === "Escape") { + completions_el.remove(); + } + }); + + // on change, update completions + element.addEventListener('input', () => { + updateCompletions(element); + }); }); }); diff --git a/ext/autocomplete/style.css b/ext/autocomplete/style.css index 7ff0f69bc6..30a0fa7b8a 100644 --- a/ext/autocomplete/style.css +++ b/ext/autocomplete/style.css @@ -1,7 +1,21 @@ -#Navigationleft .blockbody { overflow: visible; } - -.tagit { background: white !important; border: 1px solid grey !important; cursor: text; } -.tagit-choice { cursor: initial; } +.autocomplete_completions { + position: absolute; + z-index: 1000; + border: 1px solid #ccc; + color: #000; + background-color: #fff; + padding: 5px; + list-style: none; + margin: 0; + padding: 0; +} +.autocomplete_completions LI { + padding: 0.15em; +} +.autocomplete_completions .selected { + background-color: #ccc; + outline: none; +} input[name=search] ~ input[type=submit] { display: inline-block !important; } .tag-negative { background: #ff8080 !important; } diff --git a/ext/autocomplete/theme.php b/ext/autocomplete/theme.php deleted file mode 100644 index d2d170472e..0000000000 --- a/ext/autocomplete/theme.php +++ /dev/null @@ -1,19 +0,0 @@ -add_html_header(""); - $page->add_html_header(""); - $page->add_html_header(''); - $page->add_html_header(""); - } -} From 5f69545b651a8f3d648f56f7e90564cef9366f57 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 02:57:10 +0000 Subject: [PATCH 057/154] style tweak --- ext/autocomplete/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/autocomplete/style.css b/ext/autocomplete/style.css index 30a0fa7b8a..b28e1c0a8e 100644 --- a/ext/autocomplete/style.css +++ b/ext/autocomplete/style.css @@ -8,6 +8,7 @@ list-style: none; margin: 0; padding: 0; + font-size: 1rem; } .autocomplete_completions LI { padding: 0.15em; From 0eef0cc42b392cd52676dc60684fbbaece3a9098 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 03:18:31 +0000 Subject: [PATCH 058/154] [autocomplete] specify exact position --- ext/autocomplete/script.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/autocomplete/script.js b/ext/autocomplete/script.js index ad0859dd46..d224627b99 100644 --- a/ext/autocomplete/script.js +++ b/ext/autocomplete/script.js @@ -110,7 +110,10 @@ function renderCompletions(element) { // insert the completion block after the element if(element.parentNode) { element.parentNode.insertBefore(completions_el, element.nextSibling); - completions_el.style.width = element.clientWidth + 'px'; + let br = element.getBoundingClientRect(); + completions_el.style.width = br.width + 'px'; + completions_el.style.left = br.left + 'px'; + completions_el.style.top = (br.top + br.height) + 'px'; } } /** From 574087ffdb7be18ed10867593c48f4e8498b1e84 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 03:21:45 +0000 Subject: [PATCH 059/154] align --- themes/rule34v2/header.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/rule34v2/header.inc b/themes/rule34v2/header.inc index 33d3dfa4e0..5c48a68523 100644 --- a/themes/rule34v2/header.inc +++ b/themes/rule34v2/header.inc @@ -17,7 +17,7 @@ - + From 33adf9aeffa84ac439f6a414589b191eae385d22 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 03:40:30 +0000 Subject: [PATCH 060/154] [autocomplete] don't lose focus after selecting a completion --- ext/autocomplete/script.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/autocomplete/script.js b/ext/autocomplete/script.js index d224627b99..145e456540 100644 --- a/ext/autocomplete/script.js +++ b/ext/autocomplete/script.js @@ -101,8 +101,9 @@ function renderCompletions(element) { // (mousedown is used instead of click because click is // fired after blur, which causes the completion block to // be removed before the click event is handled) - li.addEventListener('mousedown', () => { + li.addEventListener('mousedown', (event) => { setCompletion(element, key); + event.preventDefault(); }); completions_el.appendChild(li); }); From a92d1ac7c5688c288744090315286566de05ab6d Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 04:11:35 +0000 Subject: [PATCH 061/154] [autocomplete] argh --- ext/autocomplete/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/autocomplete/script.js b/ext/autocomplete/script.js index 145e456540..2ae1936aa3 100644 --- a/ext/autocomplete/script.js +++ b/ext/autocomplete/script.js @@ -113,8 +113,8 @@ function renderCompletions(element) { element.parentNode.insertBefore(completions_el, element.nextSibling); let br = element.getBoundingClientRect(); completions_el.style.width = br.width + 'px'; - completions_el.style.left = br.left + 'px'; - completions_el.style.top = (br.top + br.height) + 'px'; + completions_el.style.left = window.scrollX + br.left + 'px'; + completions_el.style.top = window.scrollY + (br.top + br.height) + 'px'; } } /** From ec85a0bd763f9bf4fb19b2eaaf50aaef165ed731 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 04:18:40 +0000 Subject: [PATCH 062/154] [autocomplete] touch too --- ext/autocomplete/script.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/autocomplete/script.js b/ext/autocomplete/script.js index 2ae1936aa3..5f5f50637d 100644 --- a/ext/autocomplete/script.js +++ b/ext/autocomplete/script.js @@ -105,6 +105,10 @@ function renderCompletions(element) { setCompletion(element, key); event.preventDefault(); }); + li.addEventListener('touchstart', (event) => { + setCompletion(element, key); + event.preventDefault(); + }); completions_el.appendChild(li); }); From d04ec4296b8acf6a79a7c8fc7d09d255428a8489 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 26 Dec 2023 12:50:37 +0000 Subject: [PATCH 063/154] [upload] saner error reporting --- ext/upload/main.php | 103 ++++++++++++++++++++++++------------------- ext/upload/theme.php | 59 ++++++++++++------------- 2 files changed, 86 insertions(+), 76 deletions(-) diff --git a/ext/upload/main.php b/ext/upload/main.php index f22d55c410..dc0b8ac0ff 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -58,6 +58,15 @@ class UploadException extends SCoreException { } +class UploadError +{ + public function __construct( + public string $name, + public string $error + ) { + } +} + /** * Main upload class. * All files that are uploaded to the site are handled through this class. @@ -209,17 +218,17 @@ public function onPageRequest(PageRequestEvent $event) $source = $_POST['source'] ?? null; if (!empty($_POST["url"])) { - $image_ids = $this->try_transload($_POST["url"], [], $source, $image_id); + [$image_ids, $errors] = $this->try_transload($_POST["url"], [], $source, $image_id); $cache->delete("thumb-block:{$image_id}"); - $this->theme->display_upload_status($page, $image_ids); + $this->theme->display_upload_status($page, $image_ids, $errors); } elseif (count($_FILES) > 0) { - $image_ids = $this->try_upload($_FILES["data"], [], $source, $image_id); + [$image_ids, $errors] = $this->try_upload($_FILES["data"], [], $source, $image_id); $cache->delete("thumb-block:{$image_id}"); - $this->theme->display_upload_status($page, $image_ids); + $this->theme->display_upload_status($page, $image_ids, $errors); } elseif (!empty($_GET['url'])) { - $image_ids = $this->try_transload($_GET['url'], [], $source, $image_id); + [$image_ids, $errors] = $this->try_transload($_GET['url'], [], $source, $image_id); $cache->delete("thumb-block:{$image_id}"); - $this->theme->display_upload_status($page, $image_ids); + $this->theme->display_upload_status($page, $image_ids, $errors); } else { $this->theme->display_replace_page($page, $image_id); } @@ -232,26 +241,37 @@ public function onPageRequest(PageRequestEvent $event) $this->theme->display_error(507, "Error", "Can't upload images: disk nearly full"); return; } + if(count($_POST) == 0 && empty($_GET['url'])) { + $this->theme->display_page($page); + return; + } - /* Regular Upload Image */ - if (count($_FILES) > 0 || count($_POST) > 0) { - $image_ids = []; - - foreach ($_FILES as $name => $file) { - $tags = $this->tags_for_upload_slot(int_escape(substr($name, 4))); - $source = $_POST['source'] ?? null; - $image_ids += $this->try_upload($file, $tags, $source); - } - foreach ($_POST as $name => $value) { - if (str_starts_with($name, "url") && strlen($value) > 0) { - $tags = $this->tags_for_upload_slot(int_escape(substr($name, 3))); - $source = $_POST['source'] ?? $value; - $image_ids += $this->try_transload($value, $tags, $source); - } - } + $all_image_ids = []; + $all_errors = []; + + $files = array_filter($_FILES, function ($file) { + return !empty($file['name']); + }); + $urls = array_filter($_POST, function ($value, $key) { + return str_starts_with($key, "url") && strlen($value) > 0; + }, ARRAY_FILTER_USE_BOTH); + + foreach ($files as $name => $file) { + $tags = $this->tags_for_upload_slot(int_escape(substr($name, 4))); + $source = $_POST['source'] ?? null; + [$image_ids, $errors] = $this->try_upload($file, $tags, $source); + $all_image_ids = array_merge($all_image_ids, $image_ids); + $all_errors = array_merge($all_errors, $errors); + } + foreach ($urls as $name => $value) { + $tags = $this->tags_for_upload_slot(int_escape(substr($name, 3))); + $source = $_POST['source'] ?? $value; + [$image_ids, $errors] = $this->try_transload($value, $tags, $source); + $all_image_ids = array_merge($all_image_ids, $image_ids); + $all_errors = array_merge($all_errors, $errors); + } - $this->theme->display_upload_status($page, $image_ids); - } elseif (!empty($_GET['url'])) { + if(!empty($_GET['url'])) { $url = $_GET['url']; $source = $_GET['source'] ?? $url; $tags = ['tagme']; @@ -259,11 +279,12 @@ public function onPageRequest(PageRequestEvent $event) $tags = Tag::explode($_GET['tags']); } - $image_ids = $this->try_transload($url, $tags, $source); - $this->theme->display_upload_status($page, $image_ids); - } else { - $this->theme->display_page($page); + [$image_ids, $errors] = $this->try_transload($url, $tags, $source); + $all_image_ids = array_merge($all_image_ids, $image_ids); + $all_errors = array_merge($all_errors, $errors); } + + $this->theme->display_upload_status($page, $all_image_ids, $all_errors); } } @@ -327,12 +348,13 @@ private function try_upload(array $file, array $tags, ?string $source = null, ?i } $image_ids = []; + $errors = []; $num_files = count($file['name']); $limit = $config->get_int(UploadConfig::COUNT); try { if ($num_files > $limit) { - throw new UploadException("Upload limited to $limit"); + throw new UploadException("Upload limited to $limit files at a time"); } for ($i = 0; $i < $num_files; $i++) { @@ -358,22 +380,14 @@ private function try_upload(array $file, array $tags, ?string $source = null, ?i $image_ids[] = $event->image_id; $page->add_http_header("X-Shimmie-Post-ID: " . $event->image_id); } catch (UploadException $ex) { - $this->theme->display_upload_error( - $page, - "Error with " . html_escape($file['name'][$i]), - $ex->getMessage() - ); + $errors[] = new UploadError($file['name'][$i], $ex->getMessage()); } } } catch (UploadException $ex) { - $this->theme->display_upload_error( - $page, - "Error with upload", - $ex->getMessage() - ); + $errors[] = new UploadError('unknown', $ex->getMessage()); } - return $image_ids; + return [$image_ids, $errors]; } private function try_transload(string $url, array $tags, string $source = null, ?int $replace_id = null): array @@ -381,6 +395,7 @@ private function try_transload(string $url, array $tags, string $source = null, global $page, $config, $user; $image_ids = []; + $errors = []; $tmp_filename = tempnam(ini_get('upload_tmp_dir'), "shimmie_transload"); try { @@ -419,17 +434,13 @@ private function try_transload(string $url, array $tags, string $source = null, } $image_ids[] = $event->image_id; } catch (UploadException $ex) { - $this->theme->display_upload_error( - $page, - "Error with " . html_escape($url), - $ex->getMessage() - ); + $errors[] = new UploadError($url, $ex->getMessage()); } finally { if (file_exists($tmp_filename)) { unlink($tmp_filename); } } - return $image_ids; + return [$image_ids, $errors]; } } diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 77ba47c0b3..3f6e8a8fdc 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -22,8 +22,6 @@ class UploadTheme extends Themelet { - protected bool $has_errors = false; - public function display_block(Page $page): void { $b = new Block("Upload", (string)$this->build_upload_block(), "left", 20); @@ -71,8 +69,8 @@ public function display_page(Page $page): void ); $html = emptyHTML( $form, - SMALL("(Max file size is $max_kb)"), - SMALL(BR(), "(Max total size is $max_total_kb)"), + $max_size > 0 ? SMALL("(Max file size is $max_kb)") : null, + $max_total_size > 0 ? SMALL(BR(), "(Max total size is $max_total_kb)") : null, rawHTML(" -
        - $h -
        $b
        -
        - "; - + $html = parent::sb_to_html($block); return "
        $html
        "; } } diff --git a/themes/lite/themelet.class.php b/themes/lite/themelet.class.php index 923c50d68c..fd293a38e1 100644 --- a/themes/lite/themelet.class.php +++ b/themes/lite/themelet.class.php @@ -8,9 +8,6 @@ use function MicroHTML\{A,DIV,SPAN,joinHTML}; -/** - * Class Themelet - */ class Themelet extends BaseThemelet { public function display_paginator(Page $page, string $base, ?string $query, int $page_number, int $total_pages, bool $show_random = false) diff --git a/themes/lite/user_config.theme.php b/themes/lite/user_config.theme.php index 2ad74f7868..46d748184e 100644 --- a/themes/lite/user_config.theme.php +++ b/themes/lite/user_config.theme.php @@ -4,43 +4,11 @@ namespace Shimmie2; -/** - * Class CustomSetupTheme - * - * A customised version of the Setup theme. - * - */ class CustomUserConfigTheme extends UserConfigTheme { protected function sb_to_html(SetupBlock $block): string { - $h = $block->header; - $b = $block->body; - $i = preg_replace('/[^a-zA-Z0-9]/', '_', $h) . "-setup"; - $html = " - -
        - $h -
        $b
        -
        - "; - + $html = parent::sb_to_html($block); return "
        $html
        "; } } From 2a23c235ec17b33ce808c88b34691c2d1da057c5 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:01:36 +0000 Subject: [PATCH 087/154] [video] microhtml and no-jquery --- ext/handle_video/theme.php | 56 ++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/ext/handle_video/theme.php b/ext/handle_video/theme.php index cd0e54f6c7..076b4d566d 100644 --- a/ext/handle_video/theme.php +++ b/ext/handle_video/theme.php @@ -4,17 +4,13 @@ namespace Shimmie2; +use function MicroHTML\{A, BR, VIDEO, SOURCE, emptyHTML}; + class VideoFileHandlerTheme extends Themelet { public function display_image(Page $page, Image $image) { global $config; - $ilink = $image->get_image_link(); - $thumb_url = make_http($image->get_thumb_link()); //used as fallback image - $mime = strtolower($image->get_mime()); - $autoplay = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY); - $loop = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_LOOP); - $mute = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_MUTE); $width = "auto"; if ($image->width > 1) { @@ -25,31 +21,31 @@ public function display_image(Page $page, Image $image) $height = $image->height."px"; } - $html = "Video not playing? Click here to download the file.
        "; - - //Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats - - if (MimeType::matches_array($mime, VideoFileHandler::SUPPORTED_MIME)) { - if ($mime == MimeType::WEBM) { - //Several browsers still lack WebM support sadly: https://caniuse.com/#feat=webm - $html .= ""; - } + $html = emptyHTML( + "Video not playing? ", + A(['href' => $image->get_image_link()], "Click here"), + " to download the file.", + BR(), + VIDEO( + [ + 'controls' => true, + 'class' => 'shm-main-image', + 'id' => 'main_image', + 'alt' => 'main image', + 'poster' => make_http($image->get_thumb_link()), + 'autoplay' => $config->get_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY), + 'loop' => $config->get_bool(VideoFileHandlerConfig::PLAYBACK_LOOP), + 'muted' => $config->get_bool(VideoFileHandlerConfig::PLAYBACK_MUTE), + 'style' => "height: $height; width: $width; max-width: 100%; object-fit: contain; background-color: black;", + 'onloadstart' => 'this.volume = 0.25', + ], + SOURCE([ + 'src' => $image->get_image_link(), + 'type' => strtolower($image->get_mime()) + ]) + ) + ); - $autoplay = ($autoplay ? ' autoplay' : ''); - $loop = ($loop ? ' loop' : ''); - $mute = ($mute ? ' muted' : ''); - - $html .= " - - - "; - } else { - //This should never happen, but just in case let's have a fallback.. - $html = "Video type '$mime' not recognised"; - } $page->add_block(new Block("Video", $html, "main", 10)); } } From 00a49f6541bd53fe2dd2c55228f0c90f59e8a022 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:02:31 +0000 Subject: [PATCH 088/154] [media] set $size to "unknown" if we don't know the size --- ext/media/main.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/media/main.php b/ext/media/main.php index a5b29eb681..69c4d3066b 100644 --- a/ext/media/main.php +++ b/ext/media/main.php @@ -277,6 +277,8 @@ public function onParseLinkTemplate(ParseLinkTemplateEvent $event) } elseif ($event->image->length) { $s = ((int)($event->image->length / 100)) / 10; $event->replace('$size', "{$s}s"); + } else { + $event->replace('$size', "unknown size"); } } From aeeeca75eba77274ac905cbb5a2ece0da32c6209 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:26:19 +0000 Subject: [PATCH 089/154] [docker] add ffprobe too --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 25b835ffa9..0f7971611f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG PHP_VERSION=8.2 # Install base packages which all stages (build, test, run) need FROM debian:bookworm AS base -COPY --from=mwader/static-ffmpeg:6.1 /ffmpeg /usr/local/bin/ +COPY --from=mwader/static-ffmpeg:6.1 /ffmpeg /ffprobe /usr/local/bin/ RUN apt update && \ apt upgrade -y && \ apt install -y curl && \ From 2775ec073df598d751c52736e388b0eb51510d7c Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:26:49 +0000 Subject: [PATCH 090/154] [media] simpler video stats --- ext/handle_video/main.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php index f6851ff32b..59a8c8598e 100644 --- a/ext/handle_video/main.php +++ b/ext/handle_video/main.php @@ -86,14 +86,8 @@ protected function media_check_properties(MediaCheckPropertiesEvent $event): voi break; } } - if (array_key_exists("width", $stream) && !empty($stream["width"]) - && is_numeric($stream["width"]) && intval($stream["width"]) > ($event->image->width) ?? 0) { - $event->image->width = intval($stream["width"]); - } - if (array_key_exists("height", $stream) && !empty($stream["height"]) - && is_numeric($stream["height"]) && intval($stream["height"]) > ($event->image->height) ?? 0) { - $event->image->height = intval($stream["height"]); - } + $event->image->width = max($event->image->width, @$stream["width"]); + $event->image->height = max($event->image->height, @$stream["height"]); } } $event->image->video = $video; From 410bddd24e1448ff1bd2d0cf4656cebe320de34c Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:34:00 +0000 Subject: [PATCH 091/154] [handle_pixel] remove redundant cookie flags --- ext/handle_pixel/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/handle_pixel/script.js b/ext/handle_pixel/script.js index d2d6284f45..ce09d18500 100644 --- a/ext/handle_pixel/script.js +++ b/ext/handle_pixel/script.js @@ -24,7 +24,7 @@ document.addEventListener('DOMContentLoaded', () => { $(".shm-zoomer").val(zoom_type); if (save_cookie) { - shm_cookie_set("ui-image-zoom", zoom_type, {expires: 365, samesite: "lax", path: "/"}); + shm_cookie_set("ui-image-zoom", zoom_type); } } From ccd14e38c58faed8e0e857ff8a503ad5f59333b8 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:47:02 +0000 Subject: [PATCH 092/154] [comment] reduce jquery --- ext/comment/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/comment/script.js b/ext/comment/script.js index 47023be79a..a6f15345f0 100644 --- a/ext/comment/script.js +++ b/ext/comment/script.js @@ -1,8 +1,8 @@ function replyTo(imageId, commentId, userId) { - var box = $("#comment_on_"+imageId); + var box = document.getElementById("comment_on_"+imageId); var text = "[url=site://post/view/"+imageId+"#c"+commentId+"]@"+userId+"[/url]: "; box.focus(); - box.val(box.val() + text); + box.value += text; $("#c"+commentId).highlight(); } From ab4c58e17ee7b63ea4a130e0dfc9765a4db98b61 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:47:23 +0000 Subject: [PATCH 093/154] [view] reduce jquery --- ext/view/script.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/ext/view/script.js b/ext/view/script.js index 3c8799f3e7..4d8b04327b 100644 --- a/ext/view/script.js +++ b/ext/view/script.js @@ -7,23 +7,21 @@ function joinUrlSegments(base, query) { } document.addEventListener('DOMContentLoaded', () => { + function updateAttr(selector, attr, value) { + document.querySelectorAll(selector).forEach(function(e) { + let current = e.getAttribute(attr); + let newval = joinUrlSegments(current, query); + e.setAttribute(attr, newval); + }); + } + if(document.location.hash.length > 3) { var query = document.location.hash.substring(1); - $('LINK#prevlink').attr('href', function(i, attr) { - return joinUrlSegments(attr,query); - }); - $('LINK#nextlink').attr('href', function(i, attr) { - return joinUrlSegments(attr,query); - }); - $('A#prevlink').attr('href', function(i, attr) { - return joinUrlSegments(attr,query); - }); - $('A#nextlink').attr('href', function(i, attr) { - return joinUrlSegments(attr,query); - }); - $('span#image_delete_form form').attr('action', function(i, attr) { - return joinUrlSegments(attr,query); - }); + updateAttr("LINK#prevlink", "href", query); + updateAttr("LINK#nextlink", "href", query); + updateAttr("A#prevlink", "href", query); + updateAttr("A#nextlink", "href", query); + updateAttr("span#image_delete_form form", "action", query); } }); From c8999350129f4fc50e8f5dd5fb7f1c1d491358dd Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:47:33 +0000 Subject: [PATCH 094/154] [r34] reduce jquery --- themes/rule34v2/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/themes/rule34v2/script.js b/themes/rule34v2/script.js index c58c4fab06..7897627b21 100644 --- a/themes/rule34v2/script.js +++ b/themes/rule34v2/script.js @@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => { var navHidden = false; function toggleNav() { if(navHidden) { - $('BODY').removeClass('navHidden'); + document.body.classList.remove('navHidden'); shm_cookie_set("ui-shownav", "true"); } else { - $('BODY').addClass('navHidden'); + document.body.classList.add('navHidden'); shm_cookie_set("ui-shownav", "false"); } navHidden = !navHidden; From 8121f4d6dbedac05b9a68db8472574ef4fa57a39 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:59:16 +0000 Subject: [PATCH 095/154] [cbz] remove jquery --- ext/handle_cbz/comic.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ext/handle_cbz/comic.js b/ext/handle_cbz/comic.js index 8d0f12ee30..613e9cf7af 100644 --- a/ext/handle_cbz/comic.js +++ b/ext/handle_cbz/comic.js @@ -11,8 +11,8 @@ function Comic(root, comicURL) { self.comicZip = zip; // Shimmie-specific; nullify existing back / forward - $("[rel='previous']").remove(); - $("[rel='next']").remove(); + document.querySelector("LINK[rel='previous']").remove(); + document.querySelector("LINK[rel='next']").remove(); zip.forEach(function (relativePath, file){ self.comicPages.push(relativePath); @@ -53,7 +53,8 @@ function Comic(root, comicURL) { }; this.onKeyUp = function(e) { - if ($(e.target).is('input,textarea')) { return; } + let t = e.target; + if (t.tagName === "INPUT" || t.tagName === "TEXTAREA") { return; } if (e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) { return; } if (e.keyCode === 37) {self.prev();} else if (e.keyCode === 39) {self.next();} From de96ce812994cbd384bd49e25833d42c56e57633 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:59:27 +0000 Subject: [PATCH 096/154] [bbcode] remove jquery --- ext/bbcode/script.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ext/bbcode/script.js b/ext/bbcode/script.js index 96c6ea7d89..acedba749d 100644 --- a/ext/bbcode/script.js +++ b/ext/bbcode/script.js @@ -1,18 +1,20 @@ document.addEventListener('DOMContentLoaded', () => { - $(".shm-clink").each(function(idx, elm) { - var target_id = $(elm).data("clink-sel"); - if(target_id && $(target_id).length > 0) { + document.querySelectorAll(".shm-clink").forEach(function(el) { + var target_id = el.getAttribute("data-clink-sel"); + if(target_id && document.querySelectorAll(target_id).length > 0) { // if the target comment is already on this page, don't bother // switching pages - $(elm).attr("href", target_id); + el.setAttribute("href", target_id); + // highlight it when clicked - $(elm).click(function(e) { + el.addEventListener("click", function(e) { // This needs jQuery UI $(target_id).highlight(); }); + // vanilla target name should already be in the URL tag, but this // will include the anon ID as displayed on screen - $(elm).html("@"+$(target_id+" .username").html()); + el.innerHTML = "@"+document.querySelector(target_id+" .username").innerHTML; } }); }); From b456b86ca09a67d9eb934686f8636f11a3c17103 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 13:59:53 +0000 Subject: [PATCH 097/154] [index] remove hack for ancient opera --- ext/index/script.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ext/index/script.js b/ext/index/script.js index f2b4b02d8a..5e7cb622d6 100644 --- a/ext/index/script.js +++ b/ext/index/script.js @@ -10,15 +10,6 @@ document.addEventListener('DOMContentLoaded', () => { needs_refresh = true; } } - // need to trigger a reflow in opera, because opera implements - // text-align: justify with element margins and doesn't recalculate - // these margins when part of the line disappears... - if(needs_refresh) { - $('.shm-image-list').hide( - 0, - function() {$('.shm-image-list').show();} - ); - } //Generate a random seed when using order:random $('form > input[placeholder="Search"]').parent().submit(function(e){ From 61c52c223786f768da936e5bcab31c653e56c8cc Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 14:20:03 +0000 Subject: [PATCH 098/154] [index] less jquery --- ext/index/script.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/index/script.js b/ext/index/script.js index 5e7cb622d6..a599a97e1b 100644 --- a/ext/index/script.js +++ b/ext/index/script.js @@ -29,11 +29,11 @@ document.addEventListener('DOMContentLoaded', () => { * This allows us to cache the same thumb for all query * strings, adding the query in the browser. */ - $(".shm-image-list").each(function(idx, elm) { - var query = $(this).data("query"); + document.querySelectorAll(".shm-image-list").forEach(function(list) { + var query = list.getAttribute("data-query"); if(query) { - $(this).find(".shm-thumb-link").each(function(idx2, elm2) { - $(this).attr("href", $(this).attr("href") + query); + list.querySelectorAll(".shm-thumb-link").forEach(function(thumb) { + thumb.setAttribute("href", thumb.getAttribute("href") + query); }); } }); From 159efdcce67fa974776c988a728c6027f2e4af33 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 14:20:31 +0000 Subject: [PATCH 099/154] [r34] less jquery --- ext/rule34/script.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/ext/rule34/script.js b/ext/rule34/script.js index c4afbe0dd7..6c41f30ed6 100644 --- a/ext/rule34/script.js +++ b/ext/rule34/script.js @@ -1,22 +1,25 @@ +let tnc_div = document.createElement('div'); +tnc_div.innerHTML = ` +
        +
        +

        Cookies may be used. Please read our privacy policy for more information. +

        By accepting to enter you agree to our rules and terms of service. +

        Agree / Disagree +

        +`; + + document.addEventListener('DOMContentLoaded', () => { if(shm_cookie_get("ui-tnc-agreed") !== "true" && window.location.href.indexOf("/wiki/") == -1) { - $("BODY").addClass("censored"); - $("BODY").append("
        "); - $("BODY").append(""+ - "
        "+ - "

        Cookies may be used. Please read our privacy policy for more information."+ - "

        By accepting to enter you agree to our rules and terms of service."+ - "

        Agree / Disagree"+ - "

        "+ - ""); + document.body.classList.add('censored'); + document.body.appendChild(tnc_div); } }); function tnc_agree() { shm_cookie_set("ui-tnc-agreed", "true"); - $("BODY").removeClass("censored"); - $(".tnc_bg").hide(); - $(".tnc").hide(); + document.body.classList.remove('censored'); + tnc_div.remove(); } function image_hash_ban(id) { From 48557354fcbd77662ab26b022040632208052296 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 30 Dec 2023 14:25:03 +0000 Subject: [PATCH 100/154] [upload] less jquery --- ext/upload/bookmarklet.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ext/upload/bookmarklet.js b/ext/upload/bookmarklet.js index 29c2945bfd..bf22884f86 100644 --- a/ext/upload/bookmarklet.js +++ b/ext/upload/bookmarklet.js @@ -26,11 +26,10 @@ else if(CA === 2) { // New Tags /* * Danbooru2 - * jQuery should always active here, meaning we can use jQuery in this part of the bookmarklet. */ if(document.getElementById("image-container") !== null) { - var imageContainer = $('#image-container')[0]; + var imageContainer = document.querySelector('#image-container'); if (typeof tag !== "ftp://ftp." && chk !==1) { var tag = imageContainer.getAttribute('data-tags'); } @@ -40,9 +39,9 @@ if(document.getElementById("image-container") !== null) { var rating = imageContainer.getAttribute('data-rating'); - var fileinfo = $('#sidebar > section:eq(3) > ul > :contains("Size") > a'); - var furl = "http://" + document.location.hostname + fileinfo.attr('href'); - var fs = fileinfo.text().split(" "); + var fileinfo = document.querySelector('#sidebar > section:eq(3) > ul > :contains("Size") > a'); + var furl = "http://" + document.location.hostname + fileinfo.getAttribute('href'); + var fs = fileinfo.innerText.split(" "); var filesize = (fs[1] === "MB" ? fs[0] * 1024 : fs[0]); if(supext.search(furl.match("[a-zA-Z0-9]+$")[0]) !== -1){ From a00b522f342e393ec3793f0b4222075f37dfdc87 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 01:10:03 +0000 Subject: [PATCH 101/154] [view] more microhtml --- ext/view/theme.php | 57 ++++++++++++++++++---------------- themes/rule34v2/view.theme.php | 38 ++++++++++------------- 2 files changed, 47 insertions(+), 48 deletions(-) diff --git a/ext/view/theme.php b/ext/view/theme.php index b2dc3583d1..655bc809db 100644 --- a/ext/view/theme.php +++ b/ext/view/theme.php @@ -4,6 +4,10 @@ namespace Shimmie2; +use MicroHTML\HTMLElement; + +use function MicroHTML\{A, joinHTML, TABLE, TR, TD, INPUT, emptyHTML}; + class ViewImageTheme extends Themelet { public function display_meta_headers(Image $image) @@ -52,14 +56,14 @@ protected function get_query(): ?string return $query; } - protected function build_pin(Image $image): string + protected function build_pin(Image $image): HTMLElement { $query = $this->get_query(); - $h_prev = "Prev"; - $h_index = "Index"; - $h_next = "Next"; - - return "$h_prev | $h_index | $h_next"; + return joinHTML(" | ", [ + A(["href" => make_link("post/prev/{$image->id}", $query), "id" => "prevlink"], "Prev"), + A(["href" => make_link()], "Index"), + A(["href" => make_link("post/next/{$image->id}", $query), "id" => "nextlink"], "Next"), + ]); } protected function build_navigation(Image $image): string @@ -76,36 +80,35 @@ protected function build_navigation(Image $image): string return "$h_pin
        $h_search"; } - protected function build_info(Image $image, $editor_parts): string + protected function build_info(Image $image, $editor_parts): HTMLElement { global $user; if (count($editor_parts) == 0) { - return ($image->is_locked() ? "
        [Post Locked]" : ""); + return emptyHTML($image->is_locked() ? "[Post Locked]" : ""); } - $html = make_form(make_link("post/set"))." - - - "; - foreach ($editor_parts as $part) { - $html .= $part; - } - if ( + if( (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) && $user->can(Permissions::EDIT_IMAGE_TAG) ) { - $html .= " - - "; + $editor_parts[] = TR(TD( + ["colspan" => 4], + INPUT(["class" => "view", "type" => "button", "value" => "Edit", "onclick" => "clearViewMode()"]), + INPUT(["class" => "edit", "type" => "submit", "value" => "Set"]) + )); } - $html .= " -
        - - -
        - - "; - return $html; + + return SHM_SIMPLE_FORM( + "post/set", + INPUT(["type" => "hidden", "name" => "image_id", "value" => $image->id]), + TABLE( + [ + "class" => "image_info form", + "style" => "width: 500px; max-width: 100%;" + ], + ...$editor_parts, + ), + ); } } diff --git a/themes/rule34v2/view.theme.php b/themes/rule34v2/view.theme.php index 0e4c02d19c..85a48fb6ee 100644 --- a/themes/rule34v2/view.theme.php +++ b/themes/rule34v2/view.theme.php @@ -6,40 +6,36 @@ use MicroHTML\HTMLElement; -use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A}; +use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, TABLE, INPUT, A}; class CustomViewImageTheme extends ViewImageTheme { // override to make info box always in edit mode - protected function build_info(Image $image, $editor_parts): string + protected function build_info(Image $image, $editor_parts): HTMLElement { global $user; if (count($editor_parts) == 0) { - return ($image->is_locked() ? "
        [Post Locked]" : ""); + return emptyHTML($image->is_locked() ? "[Post Locked]" : ""); } - $html = make_form(make_link("post/set"))." - - - "; - foreach ($editor_parts as $part) { - $html .= $part; - } - if ( + if( (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) && $user->can(Permissions::EDIT_IMAGE_TAG) ) { - $html .= " - - "; + $editor_parts[] = TR(TD(["colspan" => 4], INPUT(["type" => "submit", "value" => "Set"]))); } - $html .= " -
        - -
        - - "; - return $html; + + return SHM_SIMPLE_FORM( + "post/set", + INPUT(["type" => "hidden", "name" => "image_id", "value" => $image->id]), + TABLE( + [ + "class" => "image_info form", + "style" => "width: 500px; max-width: 100%;" + ], + ...$editor_parts, + ), + ); } } From b67fca51f5f0f45cdcf4728f063caa20e0e1799a Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 01:20:36 +0000 Subject: [PATCH 102/154] [view] ViewImage -> ViewPost --- ext/view/info.php | 2 +- ext/view/main.php | 4 ++-- ext/view/test.php | 2 +- ext/view/theme.php | 2 +- themes/danbooru/view.theme.php | 2 +- themes/danbooru2/view.theme.php | 2 +- themes/futaba/view.theme.php | 2 +- themes/lite/view.theme.php | 2 +- themes/rule34v2/view.theme.php | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/view/info.php b/ext/view/info.php index 27602aa087..e6cbd218fe 100644 --- a/ext/view/info.php +++ b/ext/view/info.php @@ -4,7 +4,7 @@ namespace Shimmie2; -class ViewImageInfo extends ExtensionInfo +class ViewPostInfo extends ExtensionInfo { public const KEY = "view"; diff --git a/ext/view/main.php b/ext/view/main.php index fd9852d6a4..fbed253cd5 100644 --- a/ext/view/main.php +++ b/ext/view/main.php @@ -13,9 +13,9 @@ use function MicroHTML\TH; use function MicroHTML\TD; -class ViewImage extends Extension +class ViewPost extends Extension { - /** @var ViewImageTheme */ + /** @var ViewPostTheme */ protected Themelet $theme; public function onPageRequest(PageRequestEvent $event) diff --git a/ext/view/test.php b/ext/view/test.php index 320270522c..fc6898a07b 100644 --- a/ext/view/test.php +++ b/ext/view/test.php @@ -4,7 +4,7 @@ namespace Shimmie2; -class ViewImageTest extends ShimmiePHPUnitTestCase +class ViewPostTest extends ShimmiePHPUnitTestCase { public function setUp(): void { diff --git a/ext/view/theme.php b/ext/view/theme.php index 655bc809db..e14d52ba54 100644 --- a/ext/view/theme.php +++ b/ext/view/theme.php @@ -8,7 +8,7 @@ use function MicroHTML\{A, joinHTML, TABLE, TR, TD, INPUT, emptyHTML}; -class ViewImageTheme extends Themelet +class ViewPostTheme extends Themelet { public function display_meta_headers(Image $image) { diff --git a/themes/danbooru/view.theme.php b/themes/danbooru/view.theme.php index 28facb9347..121a93dc0b 100644 --- a/themes/danbooru/view.theme.php +++ b/themes/danbooru/view.theme.php @@ -4,7 +4,7 @@ namespace Shimmie2; -class CustomViewImageTheme extends ViewImageTheme +class CustomViewPostTheme extends ViewPostTheme { public function display_page(Image $image, $editor_parts) { diff --git a/themes/danbooru2/view.theme.php b/themes/danbooru2/view.theme.php index 99e3c8607e..febb71f426 100644 --- a/themes/danbooru2/view.theme.php +++ b/themes/danbooru2/view.theme.php @@ -4,7 +4,7 @@ namespace Shimmie2; -class CustomViewImageTheme extends ViewImageTheme +class CustomViewPostTheme extends ViewPostTheme { public function display_page(Image $image, $editor_parts) { diff --git a/themes/futaba/view.theme.php b/themes/futaba/view.theme.php index eb40ad1ec0..7f4a9f19e5 100644 --- a/themes/futaba/view.theme.php +++ b/themes/futaba/view.theme.php @@ -4,7 +4,7 @@ namespace Shimmie2; -class CustomViewImageTheme extends ViewImageTheme +class CustomViewPostTheme extends ViewPostTheme { public function display_page(Image $image, $editor_parts) { diff --git a/themes/lite/view.theme.php b/themes/lite/view.theme.php index 02d55c9639..7b0f018b1a 100644 --- a/themes/lite/view.theme.php +++ b/themes/lite/view.theme.php @@ -4,7 +4,7 @@ namespace Shimmie2; -class CustomViewImageTheme extends ViewImageTheme +class CustomViewPostTheme extends ViewPostTheme { public function display_page(Image $image, $editor_parts) { diff --git a/themes/rule34v2/view.theme.php b/themes/rule34v2/view.theme.php index 85a48fb6ee..014821873e 100644 --- a/themes/rule34v2/view.theme.php +++ b/themes/rule34v2/view.theme.php @@ -8,7 +8,7 @@ use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, TABLE, INPUT, A}; -class CustomViewImageTheme extends ViewImageTheme +class CustomViewPostTheme extends ViewPostTheme { // override to make info box always in edit mode protected function build_info(Image $image, $editor_parts): HTMLElement From 89a61d34990cc8deb0d17abbee6ab690f7ec3daa Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 14:18:54 +0000 Subject: [PATCH 103/154] [themes] remove references to no-js, there is no more modernizr --- themes/danbooru/page.class.php | 2 +- themes/danbooru2/page.class.php | 2 +- themes/futaba/page.class.php | 2 +- themes/lite/page.class.php | 2 +- themes/warm/page.class.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/themes/danbooru/page.class.php b/themes/danbooru/page.class.php index b14a417264..6ff8dba7ff 100644 --- a/themes/danbooru/page.class.php +++ b/themes/danbooru/page.class.php @@ -128,7 +128,7 @@ public function render() print << - + $head_html
        diff --git a/themes/danbooru2/page.class.php b/themes/danbooru2/page.class.php index 8bc283483e..d34354a32f 100644 --- a/themes/danbooru2/page.class.php +++ b/themes/danbooru2/page.class.php @@ -129,7 +129,7 @@ public function render() print << - + $head_html
        diff --git a/themes/futaba/page.class.php b/themes/futaba/page.class.php index 4ff54ccbf5..60652ba29a 100644 --- a/themes/futaba/page.class.php +++ b/themes/futaba/page.class.php @@ -49,7 +49,7 @@ public function render() print << - + $head_html
        diff --git a/themes/lite/page.class.php b/themes/lite/page.class.php index 082a4b5f28..242ed98305 100644 --- a/themes/lite/page.class.php +++ b/themes/lite/page.class.php @@ -87,7 +87,7 @@ public function render() print << - + $head_html
        diff --git a/themes/warm/page.class.php b/themes/warm/page.class.php index 3ffcbab4a0..3147da570d 100644 --- a/themes/warm/page.class.php +++ b/themes/warm/page.class.php @@ -45,7 +45,7 @@ public function render() print << - + $head_html
        From 42fc180c25714eb7e719f4fc61486eba866ea10a Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 14:45:39 +0000 Subject: [PATCH 104/154] [docker] set BUILD_TIME/BUILD_HASH vars --- .github/workflows/publish.yml | 8 +++++++- Dockerfile | 3 ++- core/basepage.php | 2 +- core/send_event.php | 4 +++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2e8fd122cf..d3ce57c370 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,7 +16,12 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' }} steps: - - uses: actions/checkout@master + - name: Checkout + uses: actions/checkout@master + - name: Set build vars + run: | + echo "BUILD_TIME=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV + echo "BUILD_HASH=$GITHUB_SHA" >> $GITHUB_ENV - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@main with: @@ -25,4 +30,5 @@ jobs: password: ${{ secrets.DOCKER_PASSWORD }} cache: ${{ github.event_name != 'schedule' }} buildoptions: "--build-arg RUN_TESTS=false" + buildargs: BUILD_TIME,BUILD_HASH tag_semver: true diff --git a/Dockerfile b/Dockerfile index 0f7971611f..d8c80109f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,7 +53,8 @@ EXPOSE 8000 FROM base AS run EXPOSE 8000 # HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 -ENV UID=1000 GID=1000 UPLOAD_MAX_FILESIZE=50M +ARG BUILD_TIME=unknown BUILD_HASH=unknown +ENV UID=1000 GID=1000 UPLOAD_MAX_FILESIZE=50M BUILD_TIME=${BUILD_TIME} BUILD_HASH=${BUILD_HASH} COPY --from=build /app /app WORKDIR /app ENTRYPOINT ["/app/.docker/entrypoint.sh"] diff --git a/core/basepage.php b/core/basepage.php index b4dc0474a0..18617e69f8 100644 --- a/core/basepage.php +++ b/core/basepage.php @@ -527,7 +527,7 @@ public function render() print << - + $head_html $body_html diff --git a/core/send_event.php b/core/send_event.php index e8ee335cd1..f96a3da0e1 100644 --- a/core/send_event.php +++ b/core/send_event.php @@ -21,7 +21,9 @@ function _load_event_listeners(): void { global $_shm_event_listeners; - $cache_path = data_path("cache/shm_event_listeners.php"); + $key = md5(Extension::get_enabled_extensions_as_string()); + + $cache_path = data_path("cache/shm_event_listeners.$key.php"); if (SPEED_HAX && file_exists($cache_path)) { require_once($cache_path); } else { From e3c61b72dc9b8210fbb4a509d8557a8f4007fa87 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 15:44:35 +0000 Subject: [PATCH 105/154] [core] cache event listeners based on which extensions are enabled + version number --- core/send_event.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/send_event.php b/core/send_event.php index f96a3da0e1..73af571d9b 100644 --- a/core/send_event.php +++ b/core/send_event.php @@ -21,9 +21,10 @@ function _load_event_listeners(): void { global $_shm_event_listeners; + $ver = preg_replace("/[^a-zA-Z0-9\.]/", "_", VERSION); $key = md5(Extension::get_enabled_extensions_as_string()); - $cache_path = data_path("cache/shm_event_listeners.$key.php"); + $cache_path = data_path("cache/event_listeners/el.$ver.$key.php"); if (SPEED_HAX && file_exists($cache_path)) { require_once($cache_path); } else { From 6fd1a6aeaa47f6e5c7f58ddead1e80e5b907282a Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 16:03:03 +0000 Subject: [PATCH 106/154] [docker] docs --- Dockerfile | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index d8c80109f7..3b391da1b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,15 @@ ARG PHP_VERSION=8.2 -# Install base packages which all stages (build, test, run) need +# Tree of layers: +# base +# ├── dev-tools +# │ ├── build +# │ │ └── tests +# │ └── devcontainer +# └── run (copies built artifacts out of build) + +# Install base packages +# Things which all stages (build, test, run) need FROM debian:bookworm AS base COPY --from=mwader/static-ffmpeg:6.1 /ffmpeg /ffprobe /usr/local/bin/ RUN apt update && \ @@ -14,27 +23,27 @@ RUN apt update && \ curl imagemagick zip unzip unit unit-php gettext && \ rm -rf /var/lib/apt/lists/* -# Composer has 100MB of dependencies, and we only need that during build and test -FROM base AS composer +# Install dev packages +# Things which are only needed during development - Composer has 100MB of +# dependencies, so let's avoid including that in the final image +FROM base AS dev-tools RUN apt update && apt upgrade -y && \ apt install -y composer php${PHP_VERSION}-xdebug git procps net-tools vim && \ rm -rf /var/lib/apt/lists/* ENV XDEBUG_MODE=coverage -# "Build" shimmie (composer install - done in its own stage so that we don't -# need to include all the composer fluff in the final image) -FROM composer AS build +# "Build" shimmie (composer install) +# Done in its own stage so that we don't meed to include all the +# composer fluff in the final image +FROM dev-tools AS build COPY composer.json composer.lock /app/ WORKDIR /app RUN composer install --no-dev COPY . /app/ -# Tests in their own image. Really we should inherit from app and then -# `composer install` phpunit on top of that; but for some reason -# `composer install --no-dev && composer install` doesn't install dev -FROM composer AS tests -COPY composer.json composer.lock /app/ -WORKDIR /app +# Tests in their own image. +# Re-run composer install to get dev dependencies +FROM build AS tests RUN composer install COPY . /app/ ARG RUN_TESTS=true @@ -42,11 +51,13 @@ RUN [ $RUN_TESTS = false ] || (\ echo '=== Installing ===' && mkdir -p data/config && INSTALL_DSN="sqlite:data/shimmie.sqlite" php index.php && \ echo '=== Smoke Test ===' && php index.php get-page /post/list && \ echo '=== Unit Tests ===' && ./vendor/bin/phpunit --configuration tests/phpunit.xml && \ - echo '=== Coverage ===' && XDEBUG_MODE=coverage ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \ + echo '=== Coverage ===' && ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \ echo '=== Cleaning ===' && rm -rf data) # Devcontainer target -FROM composer AS devcontainer +# Contains all of the build and debug tools, but no code, since +# that's mounted from the host +FROM dev-tools AS devcontainer EXPOSE 8000 # Actually run shimmie From 09b0f355508b58a317e43c2d1a1fb4ecf2a96e20 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 16:04:42 +0000 Subject: [PATCH 107/154] [docker] also build a 'latest' image on pushes to master --- .github/workflows/publish.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d3ce57c370..933067f7ec 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,7 +3,9 @@ name: Publish on: workflow_run: workflows: Tests - branches: main + branches: + - main + - master types: completed workflow_dispatch: push: From d4516fd6bf37a8a871b68428e4c70bf93af32b97 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 16:17:31 +0000 Subject: [PATCH 108/154] stop ignoring things which should be in the data/ dir --- .gitignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitignore b/.gitignore index 607cbefcd5..8f3636c25b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,9 @@ backup data -images -thumbs -*.phar -*.sqlite -*.cache -trace.json .docker/entrypoint.d/config.json #Composer composer.phar -composer.lock /vendor/ # Created by http://www.gitignore.io From 6df0f4f307762df45aa81a0c6a9e1b85ea472a5f Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 17:42:04 +0000 Subject: [PATCH 109/154] put runtime-generated data into data/ --- .php-cs-fixer.dist.php | 3 ++- core/testcase.php | 2 +- tests/phpunit.xml | 8 +++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 8a4d93aadc..9fd655e94e 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -14,4 +14,5 @@ 'array_syntax' => ['syntax' => 'short'], ]) ->setFinder($_phpcs_finder) -; \ No newline at end of file + ->setCacheFile("data/php-cs-fixer.cache") +; diff --git a/core/testcase.php b/core/testcase.php index cf19d48ea1..1d62e0ada6 100644 --- a/core/testcase.php +++ b/core/testcase.php @@ -52,7 +52,7 @@ public function tearDown(): void $_tracer->end(); # test $_tracer->end(); # $this->getName() $_tracer->clear(); - $_tracer->flush("tests/trace.json"); + $_tracer->flush("data/test-trace.json"); } public static function tearDownAfterClass(): void diff --git a/tests/phpunit.xml b/tests/phpunit.xml index d3420dc488..de47f9ef51 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -1,5 +1,11 @@ - + From fcdddd6f96356396d59a7e90dda191904a6241eb Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 18:41:08 +0000 Subject: [PATCH 110/154] also unlink data/test-trace.json --- tests/bootstrap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index c4344ad0ec..05dcb4979f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -13,8 +13,8 @@ require_once "core/util.php"; $_SERVER['QUERY_STRING'] = '/'; -if (file_exists("tests/trace.json")) { - unlink("tests/trace.json"); +if (file_exists("data/test-trace.json")) { + unlink("data/test-trace.json"); } global $cache, $config, $database, $user, $page, $_tracer; From fb49b785ef814f12933e8ff690665c2b2f7ff65a Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 18:48:49 +0000 Subject: [PATCH 111/154] [core] don't take shortcuts when going next/prev, as some extensions (eg ratings) might want to alter search results (fixes #984) --- core/imageboard/image.php | 19 ++++----------- ext/rating/test.php | 49 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/core/imageboard/image.php b/core/imageboard/image.php index 12bbaae607..846a0b6009 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -166,21 +166,10 @@ public function get_next(array $tags = [], bool $next = true): ?Image $dir = "ASC"; } - if (count($tags) === 0) { - $row = $database->get_row(' - SELECT images.* - FROM images - WHERE images.id '.$gtlt.' '.$this->id.' - ORDER BY images.id '.$dir.' - LIMIT 1 - '); - return ($row ? new Image($row) : null); - } else { - $tags[] = 'id'. $gtlt . $this->id; - $tags[] = 'order:id_'. strtolower($dir); - $images = Search::find_images(0, 1, $tags); - return (count($images) > 0) ? $images[0] : null; - } + $tags[] = 'id'. $gtlt . $this->id; + $tags[] = 'order:id_'. strtolower($dir); + $images = Search::find_images(0, 1, $tags); + return (count($images) > 0) ? $images[0] : null; } /** diff --git a/ext/rating/test.php b/ext/rating/test.php index e9b73473df..999758349c 100644 --- a/ext/rating/test.php +++ b/ext/rating/test.php @@ -38,4 +38,53 @@ public function testRatingExplicit() $this->log_out(); $this->assert_search_results(["pbx"], []); } + + public function testUserConfig() + { + global $config, $user_config; + + // post a safe image and an explicit image + $this->log_in_as_user(); + $image_id_e = $this->post_image("tests/bedroom_workshop.jpg", "pbx"); + $image_e = Image::by_id($image_id_e); + send_event(new RatingSetEvent($image_e, "e")); + $image_id_s = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); + $image_s = Image::by_id($image_id_s); + send_event(new RatingSetEvent($image_s, "s")); + + // user is allowed to see all + $config->set_array("ext_rating_user_privs", ["s", "q", "e"]); + + // user prefers safe-only by default + $user_config->set_array(RatingsConfig::USER_DEFAULTS, ["s"]); + + // search with no tags should return only safe image + $this->assert_search_results([], [$image_id_s]); + + // specifying a rating should return only that rating + $this->assert_search_results(["rating=e"], [$image_id_e]); + $this->assert_search_results(["rating=s"], [$image_id_s]); + + // If user prefers to see all images, going to the safe image + // and clicking next should show the explicit image + $user_config->set_array(RatingsConfig::USER_DEFAULTS, ["s", "q", "e"]); + $this->assertEquals($image_s->get_next()->id, $image_id_e); + + // If the user prefers to see only safe images by default, then + // going to the safe image and clicking next should not show + // the explicit image (See bug #984) + $user_config->set_array(RatingsConfig::USER_DEFAULTS, ["s"]); + $this->assertEquals($image_s->get_next(), null); + } + + // reset the user config to defaults at the end of every test so + // that it doesn't mess with other unrelated tests + public function tearDown(): void + { + global $config, $user_config; + $config->set_array("ext_rating_user_privs", ["?", "s", "q", "e"]); + + $this->log_in_as_user(); + $user_config->set_array(RatingsConfig::USER_DEFAULTS, ["?", "s", "q", "e"]); + } } From 625406e5917e59b9c4783a69f54d86a11eabc593 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 18:48:56 +0000 Subject: [PATCH 112/154] extra comments --- core/testcase.php | 2 +- ext/rating/main.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/core/testcase.php b/core/testcase.php index 1d62e0ada6..672f06fbde 100644 --- a/core/testcase.php +++ b/core/testcase.php @@ -157,7 +157,7 @@ protected function page_to_text(string $section = null): string } elseif ($page->mode == PageMode::DATA) { return $page->data; } else { - $this->fail("Page mode is not PAGE or DATA"); + $this->fail("Page mode is {$page->mode->name} (only PAGE and DATA are supported)"); } } diff --git a/ext/rating/main.php b/ext/rating/main.php index d02f5e27b9..34a1e4a604 100644 --- a/ext/rating/main.php +++ b/ext/rating/main.php @@ -427,6 +427,11 @@ public static function get_ratings_dict(array $ratings = null): array ); } + /** + * Figure out which ratings a user is allowed to see + * + * @return string[] + */ public static function get_user_class_privs(User $user): array { global $config; @@ -434,6 +439,12 @@ public static function get_user_class_privs(User $user): array return $config->get_array("ext_rating_".$user->class->name."_privs"); } + /** + * Figure out which ratings a user would like to see by default + * (Which will be a subset of what they are allowed to see) + * + * @return string[] + */ public static function get_user_default_ratings(): array { global $user_config, $user; From ab376f4dffa00fa1dca7f43466d40f7b50f10c44 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 19:54:14 +0000 Subject: [PATCH 113/154] [r34] remove redundant overrides --- themes/rule34v2/tag_edit.theme.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/themes/rule34v2/tag_edit.theme.php b/themes/rule34v2/tag_edit.theme.php index 88725ce811..858973ae97 100644 --- a/themes/rule34v2/tag_edit.theme.php +++ b/themes/rule34v2/tag_edit.theme.php @@ -10,21 +10,6 @@ class CustomTagEditTheme extends TagEditTheme { - public function display_mass_editor() - { - global $page; - $html = " - ".make_form(make_link("tag_edit/replace"))." - - - - -
        Search
        Replace
        - - "; - $page->add_block(new Block("Mass Tag Edit", $html)); - } - public function get_tag_editor_html(Image $image): HTMLElement { global $user; @@ -34,7 +19,7 @@ public function get_tag_editor_html(Image $image): HTMLElement "type" => "text", "name" => "tag_edit__tags", "value" => $image->get_tag_list(), - "autocomplete" => "off" + "class" => "autocomplete_tags" ]) ); } From 448ce2fad41f333dfe8705e9d129ef72779f0233 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 21:58:38 +0000 Subject: [PATCH 114/154] remove dead css --- themes/rule34v2/style.css | 8 -------- 1 file changed, 8 deletions(-) diff --git a/themes/rule34v2/style.css b/themes/rule34v2/style.css index 94024683e8..ce1481b5cd 100644 --- a/themes/rule34v2/style.css +++ b/themes/rule34v2/style.css @@ -232,14 +232,6 @@ SECTION>H3 { color: red; } -UL.tagit { - margin: 0; -} -ul.tagit li.tagit-new { - width: 50px; -} - - [data-tags~="ai-generated"]>A>IMG { background: #BC8F8F; } [data-tags~="animated"]>A>IMG { background: #CC00CC; } [data-ext="mp4"]>A>IMG, From 209e9b4ab5dab4a914e71284774246a55f61eb1a Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 21:59:52 +0000 Subject: [PATCH 115/154] [tag edit] have image info link to history if they're active --- ext/tag_edit/theme.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ext/tag_edit/theme.php b/ext/tag_edit/theme.php index bc90538f2e..735f5bb6c6 100644 --- a/ext/tag_edit/theme.php +++ b/ext/tag_edit/theme.php @@ -55,7 +55,9 @@ public function get_tag_editor_html(Image $image): HTMLElement } return SHM_POST_INFO( - "Tags", + Extension::is_enabled(TagHistoryInfo::KEY) ? + A(["href" => make_link("tag_history/{$image->id}")], "Tags") : + "Tags", joinHTML(", ", $tag_links), $user->can(Permissions::EDIT_IMAGE_TAG) ? INPUT([ "class" => "autocomplete_tags", @@ -94,7 +96,9 @@ public function get_source_editor_html(Image $image): HTMLElement { global $user; return SHM_POST_INFO( - "Source", + Extension::is_enabled(SourceHistoryInfo::KEY) ? + A(["href" => make_link("source_history/{$image->id}")], rawHTML("Source Link")) : + rawHTML("Source Link"), DIV( ["style" => "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"], $this->format_source($image->get_source()) From 9e9225acf35d37da5d2f9208d159b65cfd8b128c Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 22:27:32 +0000 Subject: [PATCH 116/154] [core] allow SHM_POST_INFO html element to accept a link parameter --- core/microhtml.php | 9 +++++++-- ext/tag_edit/theme.php | 14 ++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/microhtml.php b/core/microhtml.php index e7f4b71e41..badd408531 100644 --- a/core/microhtml.php +++ b/core/microhtml.php @@ -154,9 +154,10 @@ function SHM_OPTION(string $value, string $text, bool $selected = false): HTMLEl } function SHM_POST_INFO( - HTMLElement|string $title, + string $title, HTMLElement|string|null $view = null, HTMLElement|string|null $edit = null, + string|null $link = null, ): HTMLElement { if(!is_null($view) && !is_null($edit)) { $show = emptyHTML( @@ -170,5 +171,9 @@ function SHM_POST_INFO( } else { $show = "???"; } - return TR(TH(["width" => "50px"], $title), TD($show)); + return TR( + ["data-row"=>$title], + TH(["width" => "50px"], $link ? A(["href" => $link], $title) : $title), + TD($show) + ); } diff --git a/ext/tag_edit/theme.php b/ext/tag_edit/theme.php index 735f5bb6c6..37dfcc696d 100644 --- a/ext/tag_edit/theme.php +++ b/ext/tag_edit/theme.php @@ -55,9 +55,7 @@ public function get_tag_editor_html(Image $image): HTMLElement } return SHM_POST_INFO( - Extension::is_enabled(TagHistoryInfo::KEY) ? - A(["href" => make_link("tag_history/{$image->id}")], "Tags") : - "Tags", + "Tags", joinHTML(", ", $tag_links), $user->can(Permissions::EDIT_IMAGE_TAG) ? INPUT([ "class" => "autocomplete_tags", @@ -66,7 +64,8 @@ public function get_tag_editor_html(Image $image): HTMLElement "value" => $image->get_tag_list(), "id" => "tag_editor", "autocomplete" => "off" - ]) : null + ]) : null, + link: Extension::is_enabled(TagHistoryInfo::KEY) ? make_link("tag_history/{$image->id}") : null, ); } @@ -96,14 +95,13 @@ public function get_source_editor_html(Image $image): HTMLElement { global $user; return SHM_POST_INFO( - Extension::is_enabled(SourceHistoryInfo::KEY) ? - A(["href" => make_link("source_history/{$image->id}")], rawHTML("Source Link")) : - rawHTML("Source Link"), + "Source Link", DIV( ["style" => "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"], $this->format_source($image->get_source()) ), - $user->can(Permissions::EDIT_IMAGE_SOURCE) ? INPUT(["type" => "text", "name" => "tag_edit__source", "value" => $image->get_source()]) : null + $user->can(Permissions::EDIT_IMAGE_SOURCE) ? INPUT(["type" => "text", "name" => "tag_edit__source", "value" => $image->get_source()]) : null, + link: Extension::is_enabled(SourceHistoryInfo::KEY) ? make_link("source_history/{$image->id}") : null, ); } From d3cf2b953172f9a5270289493f4f8f1db0bb3e70 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 22:28:10 +0000 Subject: [PATCH 117/154] [r34] use CSS to show and hide things rather than custom theme overrides --- themes/rule34v2/style.css | 16 ++++++ themes/rule34v2/tag_edit.theme.php | 79 ------------------------------ themes/rule34v2/view.theme.php | 41 ---------------- 3 files changed, 16 insertions(+), 120 deletions(-) delete mode 100644 themes/rule34v2/tag_edit.theme.php delete mode 100644 themes/rule34v2/view.theme.php diff --git a/themes/rule34v2/style.css b/themes/rule34v2/style.css index ce1481b5cd..84a8ebe34e 100644 --- a/themes/rule34v2/style.css +++ b/themes/rule34v2/style.css @@ -243,6 +243,22 @@ SECTION>H3 { margin: auto; } +/* + * Image info - show both edit and view modes at the same time, + * except for Tags, Locked, and the Edit button. + */ +.image_info.infomode-view .edit, +.image_info.infomode-view .view { + display: block; +} +.image_info.infomode-view TR[data-row="Tags"] .view, +.image_info.infomode-view TR[data-row="Locked"] .view, +.image_info INPUT[type="button"][value="Edit"] { + display: none; +} + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * responsive overrides * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ diff --git a/themes/rule34v2/tag_edit.theme.php b/themes/rule34v2/tag_edit.theme.php deleted file mode 100644 index 858973ae97..0000000000 --- a/themes/rule34v2/tag_edit.theme.php +++ /dev/null @@ -1,79 +0,0 @@ - "text", - "name" => "tag_edit__tags", - "value" => $image->get_tag_list(), - "class" => "autocomplete_tags" - ]) - ); - } - - public function get_source_editor_html(Image $image): HTMLElement - { - global $user; - return SHM_POST_INFO( - A(["href" => make_link("source_history/{$image->id}")], rawHTML("Source Link")), - emptyHTML( - DIV( - ["style" => "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"], - $this->format_source($image->get_source()) - ), - $user->can(Permissions::EDIT_IMAGE_SOURCE) ? INPUT(["type" => "text", "name" => "tag_edit__source", "value" => $image->get_source()]) : null - ) - ); - } - - public function get_user_editor_html(Image $image): HTMLElement - { - global $user; - $owner = $image->get_owner()->name; - $date = rawHTML(autodate($image->posted)); - $ip = $user->can(Permissions::VIEW_IP) ? rawHTML(" (" . show_ip($image->owner_ip, "Post posted {$image->posted}") . ")") : ""; - $info = SHM_POST_INFO( - "Uploader", - emptyHTML( - A(["class" => "username", "href" => make_link("user/$owner")], $owner), - $ip, - ", ", - $date, - $user->can(Permissions::EDIT_IMAGE_OWNER) ? INPUT(["type" => "text", "name" => "tag_edit__owner", "value" => $owner]) : null - ), - ); - // SHM_POST_INFO returns a TR, let's sneakily append - // a TD with the avatar in it - $info->appendChild( - TD( - ["width" => "80px", "rowspan" => "4"], - rawHTML($image->get_owner()->get_avatar_html()) - ) - ); - return $info; - } - - public function get_lock_editor_html(Image $image): HTMLElement - { - global $user; - return SHM_POST_INFO( - "Locked", - $user->can(Permissions::EDIT_IMAGE_LOCK) ? - INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()]) : - emptyHTML($image->is_locked() ? "Yes (Only admins may edit these details)" : "No") - ); - } -} diff --git a/themes/rule34v2/view.theme.php b/themes/rule34v2/view.theme.php deleted file mode 100644 index 014821873e..0000000000 --- a/themes/rule34v2/view.theme.php +++ /dev/null @@ -1,41 +0,0 @@ -is_locked() ? "[Post Locked]" : ""); - } - - if( - (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) && - $user->can(Permissions::EDIT_IMAGE_TAG) - ) { - $editor_parts[] = TR(TD(["colspan" => 4], INPUT(["type" => "submit", "value" => "Set"]))); - } - - return SHM_SIMPLE_FORM( - "post/set", - INPUT(["type" => "hidden", "name" => "image_id", "value" => $image->id]), - TABLE( - [ - "class" => "image_info form", - "style" => "width: 500px; max-width: 100%;" - ], - ...$editor_parts, - ), - ); - } -} From 42a2d6e12f55592e0520cedeed01974b04198a1f Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 23:07:54 +0000 Subject: [PATCH 118/154] [home] show text counter even without graphical counter --- ext/home/main.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ext/home/main.php b/ext/home/main.php index 081eb05095..8a02bce6d3 100644 --- a/ext/home/main.php +++ b/ext/home/main.php @@ -52,12 +52,10 @@ private function get_body(): string } $counter_dir = $config->get_string('home_counter', 'default'); - $num_comma = ""; + $total = Search::count_images(); + $num_comma = number_format($total); $counter_text = ""; if ($counter_dir != 'none') { - $total = Search::count_images(); - $num_comma = number_format($total); - if ($counter_dir != 'text-only') { $strtotal = "$total"; $length = strlen($strtotal); From 4905bf6cc6f0d205588a29f54ac5670438dbf368 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 23:08:15 +0000 Subject: [PATCH 119/154] [r34] remove theme override which duplicates a config setting --- themes/rule34v2/home.theme.php | 61 ---------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 themes/rule34v2/home.theme.php diff --git a/themes/rule34v2/home.theme.php b/themes/rule34v2/home.theme.php deleted file mode 100644 index 1c1831b643..0000000000 --- a/themes/rule34v2/home.theme.php +++ /dev/null @@ -1,61 +0,0 @@ -$main_links
        "; - $message_html = empty($main_text) ? "" : "
        $main_text
        "; - $counter_html = empty($counter_text) ? "" : "
        $counter_text
        "; - $contact_link = empty($contact_link) ? "" : "
        Contact –"; - $search_html = " - - "; - return " -
        -

        $sitename

        - $main_links_html - $search_html - $message_html - $counter_html - -
        "; - } -} From 0f52b54667735424e0280c87a558c34e16ab7487 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 23:08:53 +0000 Subject: [PATCH 120/154] format --- core/microhtml.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/microhtml.php b/core/microhtml.php index badd408531..c7848900b8 100644 --- a/core/microhtml.php +++ b/core/microhtml.php @@ -172,7 +172,7 @@ function SHM_POST_INFO( $show = "???"; } return TR( - ["data-row"=>$title], + ["data-row" => $title], TH(["width" => "50px"], $link ? A(["href" => $link], $title) : $title), TD($show) ); From e56e166a76c3d5c89d3bbee601021bc2d37ee78e Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 23:55:27 +0000 Subject: [PATCH 121/154] [upload] give filesize-tracker script some polish --- ext/upload/script.js | 56 ++++++++++++++++++++++ ext/upload/theme.php | 79 ++++++++------------------------ themes/rule34v2/upload.theme.php | 75 ------------------------------ 3 files changed, 74 insertions(+), 136 deletions(-) create mode 100644 ext/upload/script.js diff --git a/ext/upload/script.js b/ext/upload/script.js new file mode 100644 index 0000000000..14eef320db --- /dev/null +++ b/ext/upload/script.js @@ -0,0 +1,56 @@ +function fileSize(size) { + var i = Math.floor(Math.log(size) / Math.log(1024)); + return (size / Math.pow(1024, i)).toFixed(2) * 1 + ['B', 'kB', 'MB', 'GB', 'TB'][i]; +} +function updateTracker() { + var size = 0; + var upbtn = document.getElementById("uploadbutton"); + var tracker = document.getElementById("upload_size_tracker"); + var lockbtn = false; + + // check that each individual file is less than the max file size + document.querySelectorAll("#large_upload_form input[type='file']").forEach((input) => { + var cancelbtn = document.getElementById("cancel"+input.id); + var toobig = false; + if (input.files.length) { + if(cancelbtn) cancelbtn.style.visibility = 'visible'; + for (var i = 0; i < input.files.length; i++) { + size += input.files[i].size; + if (window.shm_max_size > 0 && input.files[i].size > window.shm_max_size) { + toobig = true; + } + } + if (toobig) { + lockbtn = true; + input.style = 'color:red'; + } else { + input.style = 'color:initial'; + } + } else { + if(cancelbtn) cancelbtn.style.visibility = 'hidden'; + input.style = 'color:initial'; + } + }); + + // check that the total is less than the max total size + if (size) { + tracker.innerText = fileSize(size); + if (window.shm_max_total_size > 0 && size > window.shm_max_total_size) { + lockbtn = true; + tracker.style = 'color:red'; + } else { + tracker.style = 'color:initial'; + } + } else { + tracker.innerText = '0MB'; + } + upbtn.disabled = lockbtn; +} +document.addEventListener('DOMContentLoaded', () => { + if(document.getElementById("upload_size_tracker")) { + document.querySelectorAll("#large_upload_form input[type='file']").forEach((el) => { + el.addEventListener('change', updateTracker); + }); + updateTracker(); + } +}); diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 95718e2ff9..7c9f1c4d28 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -17,6 +17,7 @@ use function MicroHTML\DIV; use function MicroHTML\BR; use function MicroHTML\A; +use function MicroHTML\SPAN; use function MicroHTML\P; @@ -43,7 +44,7 @@ public function display_page(Page $page): void $max_kb = to_shorthand_int($max_size); $max_total_size = parse_shorthand_int(ini_get('post_max_size')) - 102400; //leave room for http request data $max_total_kb = to_shorthand_int($max_total_size); - $upload_list = $this->h_upload_list_1(); + $upload_list = $this->build_upload_list(); $form = SHM_FORM("upload", "POST", true, "file_upload"); $form->appendChild( @@ -58,10 +59,6 @@ public function display_page(Page $page): void TD(["colspan" => "5"], INPUT(["name" => "source", "type" => "text"])) ), $upload_list, - TR( - TD(["colspan" => $tl_enabled ? 2 : 4,"id" => "upload_size_tracker"], ""), - TD(["colspan" => 2], ""), - ), TR( TD(["colspan" => "6"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"])) ), @@ -69,61 +66,17 @@ public function display_page(Page $page): void ); $html = emptyHTML( $form, - $max_size > 0 ? SMALL("(Max file size is $max_kb)") : null, - $max_total_size > 0 ? SMALL(BR(), "(Max total size is $max_total_kb)") : null, + SMALL( + "(", + $max_size > 0 ? "Per-file limit: $max_kb" : null, + $max_total_size > 0 ? " / Total limit: $max_total_kb" : null, + " / Current total: ", + SPAN(["id" => "upload_size_tracker"], "0KB"), + ")" + ), rawHTML("") ); @@ -136,7 +89,7 @@ function updateTracker(){ } } - protected function h_upload_list_1(): HTMLElement + protected function build_upload_list(): HTMLElement { global $config; $upload_list = emptyHTML(); @@ -155,7 +108,11 @@ protected function h_upload_list_1(): HTMLElement for ($i = 0; $i < $upload_count; $i++) { $upload_list->appendChild( TR( - TD(["colspan" => $tl_enabled ? 2 : 4], DIV(["name" => "canceldata{$i}[]","style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;","onclick" => "$(\"input[name='data{$i}[]']\")[0].value='';updateTracker();"], "✖"), INPUT(["type" => "file", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true])), + TD( + ["colspan" => $tl_enabled ? 2 : 4], + DIV(["id" => "canceldata{$i}", "style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;","onclick" => "document.getElementById('data{$i}').value='';updateTracker();"], "✖"), + INPUT(["type" => "file", "id"=>"data{$i}", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true]) + ), $tl_enabled ? TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "url{$i}"])) : emptyHTML(), TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "tags{$i}", "class" => "autocomplete_tags"])), ) diff --git a/themes/rule34v2/upload.theme.php b/themes/rule34v2/upload.theme.php index 31b088eb1e..a40f7f4c87 100644 --- a/themes/rule34v2/upload.theme.php +++ b/themes/rule34v2/upload.theme.php @@ -35,85 +35,10 @@ public function display_full(Page $page): void $page->add_block(new Block("Upload", "Disk nearly full, uploads disabled", "head", 20)); } - // override to add link to tagging guide - public function display_page(Page $page): void - { - global $config, $page; - - $tl_enabled = ($config->get_string(UploadConfig::TRANSLOAD_ENGINE, "none") != "none"); - $max_size = $config->get_int(UploadConfig::SIZE); - $max_kb = to_shorthand_int($max_size); - $upload_list = $this->h_upload_list_1(); - - $form = SHM_FORM("upload", "POST", true, "file_upload"); - $form->appendChild( - TABLE( - ["id" => "large_upload_form", "class" => "vert"], - TR( - TD(["width" => "20"], rawHTML("Common Tags")), - TD(["colspan" => "5"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "autocomplete" => "off"])) - ), - TR( - TD(["width" => "20"], rawHTML("Common Source")), - TD(["colspan" => "5"], INPUT(["name" => "source", "type" => "text"])) - ), - $upload_list, - TR( - TD(["colspan" => "6"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"])) - ), - ) - ); - $html = emptyHTML( - $form, - SMALL("(Max file size is $max_kb)") - ); - - $page->set_title("Upload"); - $page->set_heading("Upload"); - $page->add_block(new NavBlock()); - $page->add_block(new Block("Upload", $html, "main", 20)); - if ($tl_enabled) { - $page->add_block(new Block("Bookmarklets", (string)$this->h_bookmarklets(), "left", 20)); - } - $html = " - Tagging Guide - "; - $page->add_block(new Block(null, $html, "main", 19)); - } - // override to remove small uploader and just show a link to // the big page protected function build_upload_block(): HTMLElement { return A(["href" => make_link("upload"), "style" => 'font-size: 2rem; display: block;'], "Upload"); } - - protected function h_upload_list_1(): HTMLElement - { - global $config; - $upload_list = emptyHTML(); - $upload_count = $config->get_int(UploadConfig::COUNT); - $tl_enabled = ($config->get_string(UploadConfig::TRANSLOAD_ENGINE, "none") != "none"); - $accept = $this->get_accept(); - - $upload_list->appendChild( - TR( - TD(["colspan" => $tl_enabled ? 2 : 4], "Files"), - $tl_enabled ? TD(["colspan" => "2"], "URLs") : emptyHTML(), - TD(["colspan" => "2"], "Post-Specific Tags"), - ) - ); - - for ($i = 0; $i < $upload_count; $i++) { - $upload_list->appendChild( - TR( - TD(["colspan" => $tl_enabled ? 2 : 4], INPUT(["type" => "file", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true])), - $tl_enabled ? TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "url{$i}"])) : emptyHTML(), - TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "tags{$i}", "autocomplete" => "off"])), - ) - ); - } - - return $upload_list; - } } From a906c3c457b2de75ab17dc6a057029df9ef7b7f6 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 00:53:46 +0000 Subject: [PATCH 122/154] nits --- .devcontainer/devcontainer.json | 3 ++- ext/upload/script.js | 2 +- ext/upload/theme.php | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 43bbf0a1b2..968d7a846f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -21,7 +21,8 @@ "postCreateCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", "containerEnv": { "UID": "2000", - "GID": "2000" + "GID": "2000", + "UPLOAD_MAX_FILESIZE": "50M", }, "customizations": { "vscode": { diff --git a/ext/upload/script.js b/ext/upload/script.js index 14eef320db..cae8367891 100644 --- a/ext/upload/script.js +++ b/ext/upload/script.js @@ -15,7 +15,7 @@ function updateTracker() { if (input.files.length) { if(cancelbtn) cancelbtn.style.visibility = 'visible'; for (var i = 0; i < input.files.length; i++) { - size += input.files[i].size; + size += input.files[i].size + 1024; // extra buffer for metadata if (window.shm_max_size > 0 && input.files[i].size > window.shm_max_size) { toobig = true; } diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 7c9f1c4d28..728b048c96 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -42,7 +42,7 @@ public function display_page(Page $page): void $tl_enabled = ($config->get_string(UploadConfig::TRANSLOAD_ENGINE, "none") != "none"); $max_size = $config->get_int(UploadConfig::SIZE); $max_kb = to_shorthand_int($max_size); - $max_total_size = parse_shorthand_int(ini_get('post_max_size')) - 102400; //leave room for http request data + $max_total_size = parse_shorthand_int(ini_get('post_max_size')); $max_total_kb = to_shorthand_int($max_total_size); $upload_list = $this->build_upload_list(); @@ -172,7 +172,7 @@ function() { '; $html2 = P( A(["href" => $js], $title), - rawHTML("(Click when looking at a post page. Works on sites running Shimmie / Danbooru / Gelbooru. (This also grabs the tags / rating / source!))"), + rawHTML(" (Click when looking at a post page. Works on sites running Shimmie / Danbooru / Gelbooru. (This also grabs the tags / rating / source!))"), ); return emptyHTML($html1, $html2); From 296aa403d59a27ef042b6027b7defcaeb36d9a93 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 00:56:56 +0000 Subject: [PATCH 123/154] remove unused images --- ext/upload/minus.png | Bin 212 -> 0 bytes ext/upload/plus.png | Bin 269 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ext/upload/minus.png delete mode 100644 ext/upload/plus.png diff --git a/ext/upload/minus.png b/ext/upload/minus.png deleted file mode 100644 index 57c86dec53054cc72ef0452fb7718bbc2e205be9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^{2~)xwMhD znL)t$V!5F82G3JItw%CKzG!e8IHxTzU=1m0n4-4v$*EsOiQz)wUkjY~bC?H&%Eq<7 z=@gUS!+&$mlI{Mc3hFkhH)k0pSzhM(YJXDvVJXAKB`RVCdTLofTNpfD{an^LB{Ts5 DG$%k} diff --git a/ext/upload/plus.png b/ext/upload/plus.png deleted file mode 100644 index d7ec0ae3036fcfceb702ee3787cc2e6ddf410d1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^{2L&MZ!+2Ho1iJ6}se26bfKAWr&*O?9_h$NA0ETrpy1 Date: Mon, 1 Jan 2024 02:32:13 +0000 Subject: [PATCH 124/154] Separate out GET and POST more explicitly - No longer allow uploading directly via GET, that is terrible for security. Instead, use the GET parameters to pre-fill the upload form. - PageRequestEvent has a `method` property that can be checked in extensions --- core/event.php | 5 +- core/testcase.php | 8 +-- ext/admin/main.php | 5 +- ext/image/main.php | 12 ----- ext/image/test.php | 12 +---- ext/image/theme.php | 8 ++- ext/log_console/main.php | 3 +- ext/upload/main.php | 107 ++++++++++++++++----------------------- ext/upload/test.php | 2 +- ext/upload/theme.php | 62 +++++++++++++++++------ index.php | 2 +- 11 files changed, 108 insertions(+), 118 deletions(-) diff --git a/core/event.php b/core/event.php index 464f601d36..20464d7cd9 100644 --- a/core/event.php +++ b/core/event.php @@ -46,6 +46,7 @@ class InitExtEvent extends Event */ class PageRequestEvent extends Event { + public string $method; /** * @var string[] */ @@ -53,11 +54,13 @@ class PageRequestEvent extends Event public int $arg_count; public int $part_count; - public function __construct(string $path) + public function __construct(string $method, string $path) { parent::__construct(); global $config; + $this->method = $method; + // trim starting slashes $path = ltrim($path, "/"); diff --git a/core/testcase.php b/core/testcase.php index 672f06fbde..f536e92b4f 100644 --- a/core/testcase.php +++ b/core/testcase.php @@ -86,7 +86,7 @@ private static function check_args(?array $args): array return $args; } - protected static function request($page_name, $get_args = null, $post_args = null): Page + protected static function request($method, $page_name, $get_args = null, $post_args = null): Page { // use a fresh page global $page; @@ -100,7 +100,7 @@ protected static function request($page_name, $get_args = null, $post_args = nul $_GET = $get_args; $_POST = $post_args; $page = new Page(); - send_event(new PageRequestEvent($page_name)); + send_event(new PageRequestEvent($method, $page_name)); if ($page->mode == PageMode::REDIRECT) { $page->code = 302; } @@ -109,12 +109,12 @@ protected static function request($page_name, $get_args = null, $post_args = nul protected static function get_page($page_name, $args = null): Page { - return self::request($page_name, $args, null); + return self::request("GET", $page_name, $args, null); } protected static function post_page($page_name, $args = null): Page { - return self::request($page_name, null, $args); + return self::request("POST", $page_name, null, $args); } // page things diff --git a/ext/admin/main.php b/ext/admin/main.php index f564d971ea..44b6d503f6 100644 --- a/ext/admin/main.php +++ b/ext/admin/main.php @@ -86,16 +86,15 @@ public function onCommand(CommandEvent $event) parse_str($event->args[1], $_GET); $_SERVER['REQUEST_URI'] .= "?" . $event->args[1]; } - send_event(new PageRequestEvent($event->args[0])); + send_event(new PageRequestEvent("GET", $event->args[0])); $page->display(); } if ($event->cmd == "post-page") { global $page; - $_SERVER['REQUEST_METHOD'] = "POST"; if (isset($event->args[1])) { parse_str($event->args[1], $_POST); } - send_event(new PageRequestEvent($event->args[0])); + send_event(new PageRequestEvent("POST", $event->args[0])); $page->display(); } if ($event->cmd == "get-token") { diff --git a/ext/image/main.php b/ext/image/main.php index 16c846e75e..e361f93a7d 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -95,18 +95,6 @@ public function onPageRequest(PageRequestEvent $event) } } } - } elseif ($event->page_matches("image/replace")) { - global $page, $user; - if ($user->can(Permissions::REPLACE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) { - $image = Image::by_id(int_escape($_POST['image_id'])); - if ($image) { - $page->set_mode(PageMode::REDIRECT); - $page->set_redirect(make_link('upload/replace/'.$image->id)); - } else { - /* Invalid image ID */ - throw new ImageReplaceException("Post to replace does not exist."); - } - } } elseif ($event->page_matches("image")) { $num = int_escape($event->get_arg(0)); $this->send_file($num, "image"); diff --git a/ext/image/test.php b/ext/image/test.php index 881d56a36c..3b5821ab1e 100644 --- a/ext/image/test.php +++ b/ext/image/test.php @@ -31,17 +31,7 @@ public function testDeleteRequest() $this->log_in_as_admin(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test"); $_POST['image_id'] = "$image_id"; - send_event(new PageRequestEvent("image/delete")); + send_event(new PageRequestEvent("POST", "image/delete")); $this->assertTrue(true); // FIXME: assert image was deleted? } - - public function testReplaceRequest() - { - global $page; - $this->log_in_as_admin(); - $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test"); - $_POST['image_id'] = "$image_id"; - send_event(new PageRequestEvent("image/replace")); - $this->assertEquals(PageMode::REDIRECT, $page->mode); - } } diff --git a/ext/image/theme.php b/ext/image/theme.php index 9d8fd9d47c..9ee2532717 100644 --- a/ext/image/theme.php +++ b/ext/image/theme.php @@ -26,10 +26,8 @@ public function get_deleter_html(int $image_id): string */ public function get_replace_html(int $image_id): string { - return (string)SHM_SIMPLE_FORM( - "image/replace", - INPUT(["type" => 'hidden', "name" => 'image_id', "value" => $image_id]), - INPUT(["type" => 'submit', "value" => 'Replace']), - ); + $form = SHM_FORM("replace/$image_id", "GET"); + $form->appendChild(INPUT(["type" => 'submit', "value" => 'Replace'])); + return (string)$form; } } diff --git a/ext/log_console/main.php b/ext/log_console/main.php index abfbe0bd5b..5ff8b3580a 100644 --- a/ext/log_console/main.php +++ b/ext/log_console/main.php @@ -20,13 +20,12 @@ public function onPageRequest(PageRequestEvent $event) if ( $config->get_bool("log_console_access") && - isset($_SERVER['REQUEST_METHOD']) && isset($_SERVER['REQUEST_URI']) ) { $this->log(new LogEvent( "access", SCORE_LOG_INFO, - "{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}" + "{$event->method} {$_SERVER['REQUEST_URI']}" )); } diff --git a/ext/upload/main.php b/ext/upload/main.php index dc0b8ac0ff..46bec523c0 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -192,7 +192,7 @@ public function onPageRequest(PageRequestEvent $event) } } - if ($event->page_matches("upload/replace")) { + if ($event->page_matches("replace")) { if (!$user->can(Permissions::REPLACE_IMAGE)) { $this->theme->display_error(403, "Error", "{$user->name} doesn't have permission to replace images"); return; @@ -202,35 +202,24 @@ public function onPageRequest(PageRequestEvent $event) return; } - // Try to get the image ID - if ($event->count_args() >= 1) { - $image_id = int_escape($event->get_arg(0)); - } elseif (isset($_POST['image_id'])) { - $image_id = int_escape($_POST['image_id']); - } else { - throw new UploadException("Can not replace Post: No valid Post ID given."); - } + $image_id = int_escape($event->get_arg(0)); $image_old = Image::by_id($image_id); if (is_null($image_old)) { throw new UploadException("Can not replace Post: No post with ID $image_id"); } - $source = $_POST['source'] ?? null; - - if (!empty($_POST["url"])) { - [$image_ids, $errors] = $this->try_transload($_POST["url"], [], $source, $image_id); - $cache->delete("thumb-block:{$image_id}"); - $this->theme->display_upload_status($page, $image_ids, $errors); - } elseif (count($_FILES) > 0) { - [$image_ids, $errors] = $this->try_upload($_FILES["data"], [], $source, $image_id); - $cache->delete("thumb-block:{$image_id}"); - $this->theme->display_upload_status($page, $image_ids, $errors); - } elseif (!empty($_GET['url'])) { - [$image_ids, $errors] = $this->try_transload($_GET['url'], [], $source, $image_id); + if($event->method == "GET") { + $this->theme->display_replace_page($page, $image_id); + } elseif($event->method == "POST") { + $image_ids = []; + $errors = []; + if (!empty($_POST["url"])) { + [$image_ids, $errors] = $this->try_transload($_POST["url"], [], $_POST['source'] ?? null, $image_id); + } elseif (count($_FILES) > 0) { + [$image_ids, $errors] = $this->try_upload($_FILES["data"], [], $_POST['source'] ?? null, $image_id); + } $cache->delete("thumb-block:{$image_id}"); $this->theme->display_upload_status($page, $image_ids, $errors); - } else { - $this->theme->display_replace_page($page, $image_id); } } elseif ($event->page_matches("upload")) { if (!$user->can(Permissions::CREATE_IMAGE)) { @@ -241,50 +230,39 @@ public function onPageRequest(PageRequestEvent $event) $this->theme->display_error(507, "Error", "Can't upload images: disk nearly full"); return; } - if(count($_POST) == 0 && empty($_GET['url'])) { - $this->theme->display_page($page); - return; - } - $all_image_ids = []; - $all_errors = []; - - $files = array_filter($_FILES, function ($file) { - return !empty($file['name']); - }); - $urls = array_filter($_POST, function ($value, $key) { - return str_starts_with($key, "url") && strlen($value) > 0; - }, ARRAY_FILTER_USE_BOTH); - - foreach ($files as $name => $file) { - $tags = $this->tags_for_upload_slot(int_escape(substr($name, 4))); - $source = $_POST['source'] ?? null; - [$image_ids, $errors] = $this->try_upload($file, $tags, $source); - $all_image_ids = array_merge($all_image_ids, $image_ids); - $all_errors = array_merge($all_errors, $errors); - } - foreach ($urls as $name => $value) { - $tags = $this->tags_for_upload_slot(int_escape(substr($name, 3))); - $source = $_POST['source'] ?? $value; - [$image_ids, $errors] = $this->try_transload($value, $tags, $source); - $all_image_ids = array_merge($all_image_ids, $image_ids); - $all_errors = array_merge($all_errors, $errors); - } + if($event->method == "GET") { + $this->theme->display_page($page); + } elseif($event->method == "POST") { + $all_image_ids = []; + $all_errors = []; + + $files = array_filter($_FILES, function ($file) { + return !empty($file['name']); + }); + foreach ($files as $name => $file) { + $slot = int_escape(substr($name, 4)); + $tags = $this->tags_for_upload_slot($slot); + $source = $this->source_for_upload_slot($slot); + [$image_ids, $errors] = $this->try_upload($file, $tags, $source); + $all_image_ids = array_merge($all_image_ids, $image_ids); + $all_errors = array_merge($all_errors, $errors); + } - if(!empty($_GET['url'])) { - $url = $_GET['url']; - $source = $_GET['source'] ?? $url; - $tags = ['tagme']; - if (!empty($_GET['tags']) && $_GET['tags'] != "null") { - $tags = Tag::explode($_GET['tags']); + $urls = array_filter($_POST, function ($value, $key) { + return str_starts_with($key, "url") && strlen($value) > 0; + }, ARRAY_FILTER_USE_BOTH); + foreach ($urls as $name => $value) { + $slot = int_escape(substr($name, 3)); + $tags = $this->tags_for_upload_slot($slot); + $source = $this->source_for_upload_slot($slot); + [$image_ids, $errors] = $this->try_transload($value, $tags, $source); + $all_image_ids = array_merge($all_image_ids, $image_ids); + $all_errors = array_merge($all_errors, $errors); } - [$image_ids, $errors] = $this->try_transload($url, $tags, $source); - $all_image_ids = array_merge($all_image_ids, $image_ids); - $all_errors = array_merge($all_errors, $errors); + $this->theme->display_upload_status($page, $all_image_ids, $all_errors); } - - $this->theme->display_upload_status($page, $all_image_ids, $all_errors); } } @@ -299,6 +277,11 @@ private function tags_for_upload_slot(int $id): array ); } + private function source_for_upload_slot(int $id): ?string + { + return $_POST["source$id"] ?? $_POST['source'] ?? null; + } + /** * Returns a descriptive error message for the specified PHP error code. * diff --git a/ext/upload/test.php b/ext/upload/test.php index 9e12f99e0a..be099ba6f6 100644 --- a/ext/upload/test.php +++ b/ext/upload/test.php @@ -67,7 +67,7 @@ public function testRawReplace() ] ]; - $page = $this->post_page("upload/replace", ["image_id" => $image_id]); + $page = $this->post_page("replace/$image_id"); $this->assert_response(302); $this->assertEquals("/test/post/view/$image_id", $page->redirect); diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 728b048c96..43a45fa506 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -52,15 +52,15 @@ public function display_page(Page $page): void ["id" => "large_upload_form", "class" => "vert"], TR( TD(["width" => "20"], rawHTML("Common Tags")), - TD(["colspan" => "5"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags", "autocomplete" => "off"])) + TD(["colspan" => "6"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags"])) ), TR( TD(["width" => "20"], rawHTML("Common Source")), - TD(["colspan" => "5"], INPUT(["name" => "source", "type" => "text"])) + TD(["colspan" => "6"], INPUT(["name" => "source", "type" => "text", "placeholder" => "https://..."])) ), $upload_list, TR( - TD(["colspan" => "6"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"])) + TD(["colspan" => "7"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"])) ), ) ); @@ -85,7 +85,7 @@ public function display_page(Page $page): void $page->add_block(new NavBlock()); $page->add_block(new Block("Upload", $html, "main", 20)); if ($tl_enabled) { - $page->add_block(new Block("Bookmarklets", (string)$this->h_bookmarklets(), "left", 20)); + $page->add_block(new Block("Bookmarklets", $this->build_bookmarklets(), "left", 20)); } } @@ -99,9 +99,10 @@ protected function build_upload_list(): HTMLElement $upload_list->appendChild( TR( - TD(["colspan" => $tl_enabled ? 2 : 4], "Files"), - $tl_enabled ? TD(["colspan" => "2"], "URLs") : emptyHTML(), - TD(["colspan" => "2"], "Post-Specific Tags"), + TD(["colspan" => 2], "Select File"), + TD($tl_enabled ? "or URL" : null), + TD("Post-Specific Tags"), + TD("Post-Specific Source"), ) ); @@ -109,12 +110,42 @@ protected function build_upload_list(): HTMLElement $upload_list->appendChild( TR( TD( - ["colspan" => $tl_enabled ? 2 : 4], - DIV(["id" => "canceldata{$i}", "style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;","onclick" => "document.getElementById('data{$i}').value='';updateTracker();"], "✖"), - INPUT(["type" => "file", "id"=>"data{$i}", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true]) + ["colspan" => 2, "style" => "white-space: nowrap;"], + DIV([ + "id" => "canceldata{$i}", + "style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;", + "onclick" => "document.getElementById('data{$i}').value='';updateTracker();", + ], "✖"), + INPUT([ + "type" => "file", + "id" => "data{$i}", + "name" => "data{$i}[]", + "accept" => $accept, + "multiple" => true, + ]), + ), + TD( + $tl_enabled ? INPUT([ + "type" => "text", + "name" => "url{$i}", + "value" => ($i == 0) ? @$_GET['url'] : null, + ]) : null + ), + TD( + INPUT([ + "type" => "text", + "name" => "tags{$i}", + "class" => "autocomplete_tags", + "value" => ($i == 0) ? @$_GET['tags'] : null, + ]) + ), + TD( + INPUT([ + "type" => "text", + "name" => "source{$i}", + "value" => ($i == 0) ? @$_GET['source'] : null, + ]) ), - $tl_enabled ? TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "url{$i}"])) : emptyHTML(), - TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "tags{$i}", "class" => "autocomplete_tags"])), ) ); } @@ -122,7 +153,7 @@ protected function build_upload_list(): HTMLElement return $upload_list; } - protected function h_bookmarklets(): HTMLElement + protected function build_bookmarklets(): HTMLElement { global $config; $link = make_http(make_link("upload")); @@ -197,7 +228,7 @@ public function display_replace_page(Page $page, int $image_id) $upload_list->appendChild( TR( TD("or URL"), - TD(INPUT(["name" => "url", "type" => "text"])) + TD(INPUT(["name" => "url", "type" => "text", "value" => @$_GET['url']])) ) ); } @@ -208,9 +239,8 @@ public function display_replace_page(Page $page, int $image_id) $image = Image::by_id($image_id); $thumbnail = $this->build_thumb_html($image); - $form = SHM_FORM("upload/replace/".$image_id, "POST", true); + $form = SHM_FORM("replace/".$image_id, "POST", true); $form->appendChild(emptyHTML( - INPUT(["type" => "hidden", "name" => "image_id", "value" => $image_id]), TABLE( ["id" => "large_upload_form", "class" => "vert"], $upload_list, diff --git a/index.php b/index.php index 5b7cbb9604..1cc8e2c02d 100644 --- a/index.php +++ b/index.php @@ -82,7 +82,7 @@ if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') { send_event(new CommandEvent($argv)); } else { - send_event(new PageRequestEvent(_get_query())); + send_event(new PageRequestEvent($_SERVER['REQUEST_METHOD'], _get_query())); $page->display(); } From dccf63fb36325208db272d95260f8db89432f7f5 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 03:05:35 +0000 Subject: [PATCH 125/154] [devcontainer] add phpstan --- .devcontainer/devcontainer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 968d7a846f..11ca6759b0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,15 +14,13 @@ // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [8000], - // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", + "postStartCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", "containerEnv": { "UID": "2000", "GID": "2000", - "UPLOAD_MAX_FILESIZE": "50M", + "UPLOAD_MAX_FILESIZE": "50M" }, "customizations": { "vscode": { @@ -31,7 +29,8 @@ "ryanluker.vscode-coverage-gutters", "xdebug.php-debug", "DEVSENSE.phptools-vscode", - "ms-azuretools.vscode-docker" + "ms-azuretools.vscode-docker", + "SanderRonde.phpstan-vscode" ], "settings": { "phpunit.args": [ @@ -40,7 +39,8 @@ ], "coverage-gutters.coverageFileNames": [ "data/coverage.clover" - ] + ], + "phpstan.configFile": "tests/phpstan.neon" } } } From 3462a81c28c84c244093cd43daa92d3b6c0537f2 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 31 Dec 2023 23:55:27 +0000 Subject: [PATCH 126/154] [upload] give filesize-tracker script some polish --- ext/upload/script.js | 56 ++++++++++++++++++++++ ext/upload/theme.php | 79 ++++++++------------------------ themes/rule34v2/upload.theme.php | 75 ------------------------------ 3 files changed, 74 insertions(+), 136 deletions(-) create mode 100644 ext/upload/script.js diff --git a/ext/upload/script.js b/ext/upload/script.js new file mode 100644 index 0000000000..14eef320db --- /dev/null +++ b/ext/upload/script.js @@ -0,0 +1,56 @@ +function fileSize(size) { + var i = Math.floor(Math.log(size) / Math.log(1024)); + return (size / Math.pow(1024, i)).toFixed(2) * 1 + ['B', 'kB', 'MB', 'GB', 'TB'][i]; +} +function updateTracker() { + var size = 0; + var upbtn = document.getElementById("uploadbutton"); + var tracker = document.getElementById("upload_size_tracker"); + var lockbtn = false; + + // check that each individual file is less than the max file size + document.querySelectorAll("#large_upload_form input[type='file']").forEach((input) => { + var cancelbtn = document.getElementById("cancel"+input.id); + var toobig = false; + if (input.files.length) { + if(cancelbtn) cancelbtn.style.visibility = 'visible'; + for (var i = 0; i < input.files.length; i++) { + size += input.files[i].size; + if (window.shm_max_size > 0 && input.files[i].size > window.shm_max_size) { + toobig = true; + } + } + if (toobig) { + lockbtn = true; + input.style = 'color:red'; + } else { + input.style = 'color:initial'; + } + } else { + if(cancelbtn) cancelbtn.style.visibility = 'hidden'; + input.style = 'color:initial'; + } + }); + + // check that the total is less than the max total size + if (size) { + tracker.innerText = fileSize(size); + if (window.shm_max_total_size > 0 && size > window.shm_max_total_size) { + lockbtn = true; + tracker.style = 'color:red'; + } else { + tracker.style = 'color:initial'; + } + } else { + tracker.innerText = '0MB'; + } + upbtn.disabled = lockbtn; +} +document.addEventListener('DOMContentLoaded', () => { + if(document.getElementById("upload_size_tracker")) { + document.querySelectorAll("#large_upload_form input[type='file']").forEach((el) => { + el.addEventListener('change', updateTracker); + }); + updateTracker(); + } +}); diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 95718e2ff9..7c9f1c4d28 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -17,6 +17,7 @@ use function MicroHTML\DIV; use function MicroHTML\BR; use function MicroHTML\A; +use function MicroHTML\SPAN; use function MicroHTML\P; @@ -43,7 +44,7 @@ public function display_page(Page $page): void $max_kb = to_shorthand_int($max_size); $max_total_size = parse_shorthand_int(ini_get('post_max_size')) - 102400; //leave room for http request data $max_total_kb = to_shorthand_int($max_total_size); - $upload_list = $this->h_upload_list_1(); + $upload_list = $this->build_upload_list(); $form = SHM_FORM("upload", "POST", true, "file_upload"); $form->appendChild( @@ -58,10 +59,6 @@ public function display_page(Page $page): void TD(["colspan" => "5"], INPUT(["name" => "source", "type" => "text"])) ), $upload_list, - TR( - TD(["colspan" => $tl_enabled ? 2 : 4,"id" => "upload_size_tracker"], ""), - TD(["colspan" => 2], ""), - ), TR( TD(["colspan" => "6"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"])) ), @@ -69,61 +66,17 @@ public function display_page(Page $page): void ); $html = emptyHTML( $form, - $max_size > 0 ? SMALL("(Max file size is $max_kb)") : null, - $max_total_size > 0 ? SMALL(BR(), "(Max total size is $max_total_kb)") : null, + SMALL( + "(", + $max_size > 0 ? "Per-file limit: $max_kb" : null, + $max_total_size > 0 ? " / Total limit: $max_total_kb" : null, + " / Current total: ", + SPAN(["id" => "upload_size_tracker"], "0KB"), + ")" + ), rawHTML("") ); @@ -136,7 +89,7 @@ function updateTracker(){ } } - protected function h_upload_list_1(): HTMLElement + protected function build_upload_list(): HTMLElement { global $config; $upload_list = emptyHTML(); @@ -155,7 +108,11 @@ protected function h_upload_list_1(): HTMLElement for ($i = 0; $i < $upload_count; $i++) { $upload_list->appendChild( TR( - TD(["colspan" => $tl_enabled ? 2 : 4], DIV(["name" => "canceldata{$i}[]","style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;","onclick" => "$(\"input[name='data{$i}[]']\")[0].value='';updateTracker();"], "✖"), INPUT(["type" => "file", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true])), + TD( + ["colspan" => $tl_enabled ? 2 : 4], + DIV(["id" => "canceldata{$i}", "style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;","onclick" => "document.getElementById('data{$i}').value='';updateTracker();"], "✖"), + INPUT(["type" => "file", "id"=>"data{$i}", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true]) + ), $tl_enabled ? TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "url{$i}"])) : emptyHTML(), TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "tags{$i}", "class" => "autocomplete_tags"])), ) diff --git a/themes/rule34v2/upload.theme.php b/themes/rule34v2/upload.theme.php index 31b088eb1e..a40f7f4c87 100644 --- a/themes/rule34v2/upload.theme.php +++ b/themes/rule34v2/upload.theme.php @@ -35,85 +35,10 @@ public function display_full(Page $page): void $page->add_block(new Block("Upload", "Disk nearly full, uploads disabled", "head", 20)); } - // override to add link to tagging guide - public function display_page(Page $page): void - { - global $config, $page; - - $tl_enabled = ($config->get_string(UploadConfig::TRANSLOAD_ENGINE, "none") != "none"); - $max_size = $config->get_int(UploadConfig::SIZE); - $max_kb = to_shorthand_int($max_size); - $upload_list = $this->h_upload_list_1(); - - $form = SHM_FORM("upload", "POST", true, "file_upload"); - $form->appendChild( - TABLE( - ["id" => "large_upload_form", "class" => "vert"], - TR( - TD(["width" => "20"], rawHTML("Common Tags")), - TD(["colspan" => "5"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "autocomplete" => "off"])) - ), - TR( - TD(["width" => "20"], rawHTML("Common Source")), - TD(["colspan" => "5"], INPUT(["name" => "source", "type" => "text"])) - ), - $upload_list, - TR( - TD(["colspan" => "6"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"])) - ), - ) - ); - $html = emptyHTML( - $form, - SMALL("(Max file size is $max_kb)") - ); - - $page->set_title("Upload"); - $page->set_heading("Upload"); - $page->add_block(new NavBlock()); - $page->add_block(new Block("Upload", $html, "main", 20)); - if ($tl_enabled) { - $page->add_block(new Block("Bookmarklets", (string)$this->h_bookmarklets(), "left", 20)); - } - $html = " - Tagging Guide - "; - $page->add_block(new Block(null, $html, "main", 19)); - } - // override to remove small uploader and just show a link to // the big page protected function build_upload_block(): HTMLElement { return A(["href" => make_link("upload"), "style" => 'font-size: 2rem; display: block;'], "Upload"); } - - protected function h_upload_list_1(): HTMLElement - { - global $config; - $upload_list = emptyHTML(); - $upload_count = $config->get_int(UploadConfig::COUNT); - $tl_enabled = ($config->get_string(UploadConfig::TRANSLOAD_ENGINE, "none") != "none"); - $accept = $this->get_accept(); - - $upload_list->appendChild( - TR( - TD(["colspan" => $tl_enabled ? 2 : 4], "Files"), - $tl_enabled ? TD(["colspan" => "2"], "URLs") : emptyHTML(), - TD(["colspan" => "2"], "Post-Specific Tags"), - ) - ); - - for ($i = 0; $i < $upload_count; $i++) { - $upload_list->appendChild( - TR( - TD(["colspan" => $tl_enabled ? 2 : 4], INPUT(["type" => "file", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true])), - $tl_enabled ? TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "url{$i}"])) : emptyHTML(), - TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "tags{$i}", "autocomplete" => "off"])), - ) - ); - } - - return $upload_list; - } } From e3fb67661eb1f21fc2fa1f1d629a4b1a709aa087 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 00:53:46 +0000 Subject: [PATCH 127/154] nits --- .devcontainer/devcontainer.json | 3 ++- ext/upload/script.js | 2 +- ext/upload/theme.php | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 43bbf0a1b2..968d7a846f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -21,7 +21,8 @@ "postCreateCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", "containerEnv": { "UID": "2000", - "GID": "2000" + "GID": "2000", + "UPLOAD_MAX_FILESIZE": "50M", }, "customizations": { "vscode": { diff --git a/ext/upload/script.js b/ext/upload/script.js index 14eef320db..cae8367891 100644 --- a/ext/upload/script.js +++ b/ext/upload/script.js @@ -15,7 +15,7 @@ function updateTracker() { if (input.files.length) { if(cancelbtn) cancelbtn.style.visibility = 'visible'; for (var i = 0; i < input.files.length; i++) { - size += input.files[i].size; + size += input.files[i].size + 1024; // extra buffer for metadata if (window.shm_max_size > 0 && input.files[i].size > window.shm_max_size) { toobig = true; } diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 7c9f1c4d28..728b048c96 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -42,7 +42,7 @@ public function display_page(Page $page): void $tl_enabled = ($config->get_string(UploadConfig::TRANSLOAD_ENGINE, "none") != "none"); $max_size = $config->get_int(UploadConfig::SIZE); $max_kb = to_shorthand_int($max_size); - $max_total_size = parse_shorthand_int(ini_get('post_max_size')) - 102400; //leave room for http request data + $max_total_size = parse_shorthand_int(ini_get('post_max_size')); $max_total_kb = to_shorthand_int($max_total_size); $upload_list = $this->build_upload_list(); @@ -172,7 +172,7 @@ function() { '; $html2 = P( A(["href" => $js], $title), - rawHTML("(Click when looking at a post page. Works on sites running Shimmie / Danbooru / Gelbooru. (This also grabs the tags / rating / source!))"), + rawHTML(" (Click when looking at a post page. Works on sites running Shimmie / Danbooru / Gelbooru. (This also grabs the tags / rating / source!))"), ); return emptyHTML($html1, $html2); From 535600898562d272544bf8570bc703b575f214cb Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 00:56:56 +0000 Subject: [PATCH 128/154] remove unused images --- ext/upload/minus.png | Bin 212 -> 0 bytes ext/upload/plus.png | Bin 269 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ext/upload/minus.png delete mode 100644 ext/upload/plus.png diff --git a/ext/upload/minus.png b/ext/upload/minus.png deleted file mode 100644 index 57c86dec53054cc72ef0452fb7718bbc2e205be9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^{2~)xwMhD znL)t$V!5F82G3JItw%CKzG!e8IHxTzU=1m0n4-4v$*EsOiQz)wUkjY~bC?H&%Eq<7 z=@gUS!+&$mlI{Mc3hFkhH)k0pSzhM(YJXDvVJXAKB`RVCdTLofTNpfD{an^LB{Ts5 DG$%k} diff --git a/ext/upload/plus.png b/ext/upload/plus.png deleted file mode 100644 index d7ec0ae3036fcfceb702ee3787cc2e6ddf410d1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^{2L&MZ!+2Ho1iJ6}se26bfKAWr&*O?9_h$NA0ETrpy1 Date: Mon, 1 Jan 2024 02:32:13 +0000 Subject: [PATCH 129/154] Separate out GET and POST more explicitly - No longer allow uploading directly via GET, that is terrible for security. Instead, use the GET parameters to pre-fill the upload form. - PageRequestEvent has a `method` property that can be checked in extensions --- core/event.php | 5 +- core/testcase.php | 8 +-- ext/admin/main.php | 5 +- ext/image/main.php | 12 ----- ext/image/test.php | 12 +---- ext/image/theme.php | 8 ++- ext/log_console/main.php | 3 +- ext/upload/main.php | 107 ++++++++++++++++----------------------- ext/upload/test.php | 2 +- ext/upload/theme.php | 62 +++++++++++++++++------ index.php | 2 +- 11 files changed, 108 insertions(+), 118 deletions(-) diff --git a/core/event.php b/core/event.php index 464f601d36..20464d7cd9 100644 --- a/core/event.php +++ b/core/event.php @@ -46,6 +46,7 @@ class InitExtEvent extends Event */ class PageRequestEvent extends Event { + public string $method; /** * @var string[] */ @@ -53,11 +54,13 @@ class PageRequestEvent extends Event public int $arg_count; public int $part_count; - public function __construct(string $path) + public function __construct(string $method, string $path) { parent::__construct(); global $config; + $this->method = $method; + // trim starting slashes $path = ltrim($path, "/"); diff --git a/core/testcase.php b/core/testcase.php index 672f06fbde..f536e92b4f 100644 --- a/core/testcase.php +++ b/core/testcase.php @@ -86,7 +86,7 @@ private static function check_args(?array $args): array return $args; } - protected static function request($page_name, $get_args = null, $post_args = null): Page + protected static function request($method, $page_name, $get_args = null, $post_args = null): Page { // use a fresh page global $page; @@ -100,7 +100,7 @@ protected static function request($page_name, $get_args = null, $post_args = nul $_GET = $get_args; $_POST = $post_args; $page = new Page(); - send_event(new PageRequestEvent($page_name)); + send_event(new PageRequestEvent($method, $page_name)); if ($page->mode == PageMode::REDIRECT) { $page->code = 302; } @@ -109,12 +109,12 @@ protected static function request($page_name, $get_args = null, $post_args = nul protected static function get_page($page_name, $args = null): Page { - return self::request($page_name, $args, null); + return self::request("GET", $page_name, $args, null); } protected static function post_page($page_name, $args = null): Page { - return self::request($page_name, null, $args); + return self::request("POST", $page_name, null, $args); } // page things diff --git a/ext/admin/main.php b/ext/admin/main.php index f564d971ea..44b6d503f6 100644 --- a/ext/admin/main.php +++ b/ext/admin/main.php @@ -86,16 +86,15 @@ public function onCommand(CommandEvent $event) parse_str($event->args[1], $_GET); $_SERVER['REQUEST_URI'] .= "?" . $event->args[1]; } - send_event(new PageRequestEvent($event->args[0])); + send_event(new PageRequestEvent("GET", $event->args[0])); $page->display(); } if ($event->cmd == "post-page") { global $page; - $_SERVER['REQUEST_METHOD'] = "POST"; if (isset($event->args[1])) { parse_str($event->args[1], $_POST); } - send_event(new PageRequestEvent($event->args[0])); + send_event(new PageRequestEvent("POST", $event->args[0])); $page->display(); } if ($event->cmd == "get-token") { diff --git a/ext/image/main.php b/ext/image/main.php index 16c846e75e..e361f93a7d 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -95,18 +95,6 @@ public function onPageRequest(PageRequestEvent $event) } } } - } elseif ($event->page_matches("image/replace")) { - global $page, $user; - if ($user->can(Permissions::REPLACE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) { - $image = Image::by_id(int_escape($_POST['image_id'])); - if ($image) { - $page->set_mode(PageMode::REDIRECT); - $page->set_redirect(make_link('upload/replace/'.$image->id)); - } else { - /* Invalid image ID */ - throw new ImageReplaceException("Post to replace does not exist."); - } - } } elseif ($event->page_matches("image")) { $num = int_escape($event->get_arg(0)); $this->send_file($num, "image"); diff --git a/ext/image/test.php b/ext/image/test.php index 881d56a36c..3b5821ab1e 100644 --- a/ext/image/test.php +++ b/ext/image/test.php @@ -31,17 +31,7 @@ public function testDeleteRequest() $this->log_in_as_admin(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test"); $_POST['image_id'] = "$image_id"; - send_event(new PageRequestEvent("image/delete")); + send_event(new PageRequestEvent("POST", "image/delete")); $this->assertTrue(true); // FIXME: assert image was deleted? } - - public function testReplaceRequest() - { - global $page; - $this->log_in_as_admin(); - $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test"); - $_POST['image_id'] = "$image_id"; - send_event(new PageRequestEvent("image/replace")); - $this->assertEquals(PageMode::REDIRECT, $page->mode); - } } diff --git a/ext/image/theme.php b/ext/image/theme.php index 9d8fd9d47c..9ee2532717 100644 --- a/ext/image/theme.php +++ b/ext/image/theme.php @@ -26,10 +26,8 @@ public function get_deleter_html(int $image_id): string */ public function get_replace_html(int $image_id): string { - return (string)SHM_SIMPLE_FORM( - "image/replace", - INPUT(["type" => 'hidden', "name" => 'image_id', "value" => $image_id]), - INPUT(["type" => 'submit', "value" => 'Replace']), - ); + $form = SHM_FORM("replace/$image_id", "GET"); + $form->appendChild(INPUT(["type" => 'submit', "value" => 'Replace'])); + return (string)$form; } } diff --git a/ext/log_console/main.php b/ext/log_console/main.php index abfbe0bd5b..5ff8b3580a 100644 --- a/ext/log_console/main.php +++ b/ext/log_console/main.php @@ -20,13 +20,12 @@ public function onPageRequest(PageRequestEvent $event) if ( $config->get_bool("log_console_access") && - isset($_SERVER['REQUEST_METHOD']) && isset($_SERVER['REQUEST_URI']) ) { $this->log(new LogEvent( "access", SCORE_LOG_INFO, - "{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}" + "{$event->method} {$_SERVER['REQUEST_URI']}" )); } diff --git a/ext/upload/main.php b/ext/upload/main.php index dc0b8ac0ff..46bec523c0 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -192,7 +192,7 @@ public function onPageRequest(PageRequestEvent $event) } } - if ($event->page_matches("upload/replace")) { + if ($event->page_matches("replace")) { if (!$user->can(Permissions::REPLACE_IMAGE)) { $this->theme->display_error(403, "Error", "{$user->name} doesn't have permission to replace images"); return; @@ -202,35 +202,24 @@ public function onPageRequest(PageRequestEvent $event) return; } - // Try to get the image ID - if ($event->count_args() >= 1) { - $image_id = int_escape($event->get_arg(0)); - } elseif (isset($_POST['image_id'])) { - $image_id = int_escape($_POST['image_id']); - } else { - throw new UploadException("Can not replace Post: No valid Post ID given."); - } + $image_id = int_escape($event->get_arg(0)); $image_old = Image::by_id($image_id); if (is_null($image_old)) { throw new UploadException("Can not replace Post: No post with ID $image_id"); } - $source = $_POST['source'] ?? null; - - if (!empty($_POST["url"])) { - [$image_ids, $errors] = $this->try_transload($_POST["url"], [], $source, $image_id); - $cache->delete("thumb-block:{$image_id}"); - $this->theme->display_upload_status($page, $image_ids, $errors); - } elseif (count($_FILES) > 0) { - [$image_ids, $errors] = $this->try_upload($_FILES["data"], [], $source, $image_id); - $cache->delete("thumb-block:{$image_id}"); - $this->theme->display_upload_status($page, $image_ids, $errors); - } elseif (!empty($_GET['url'])) { - [$image_ids, $errors] = $this->try_transload($_GET['url'], [], $source, $image_id); + if($event->method == "GET") { + $this->theme->display_replace_page($page, $image_id); + } elseif($event->method == "POST") { + $image_ids = []; + $errors = []; + if (!empty($_POST["url"])) { + [$image_ids, $errors] = $this->try_transload($_POST["url"], [], $_POST['source'] ?? null, $image_id); + } elseif (count($_FILES) > 0) { + [$image_ids, $errors] = $this->try_upload($_FILES["data"], [], $_POST['source'] ?? null, $image_id); + } $cache->delete("thumb-block:{$image_id}"); $this->theme->display_upload_status($page, $image_ids, $errors); - } else { - $this->theme->display_replace_page($page, $image_id); } } elseif ($event->page_matches("upload")) { if (!$user->can(Permissions::CREATE_IMAGE)) { @@ -241,50 +230,39 @@ public function onPageRequest(PageRequestEvent $event) $this->theme->display_error(507, "Error", "Can't upload images: disk nearly full"); return; } - if(count($_POST) == 0 && empty($_GET['url'])) { - $this->theme->display_page($page); - return; - } - $all_image_ids = []; - $all_errors = []; - - $files = array_filter($_FILES, function ($file) { - return !empty($file['name']); - }); - $urls = array_filter($_POST, function ($value, $key) { - return str_starts_with($key, "url") && strlen($value) > 0; - }, ARRAY_FILTER_USE_BOTH); - - foreach ($files as $name => $file) { - $tags = $this->tags_for_upload_slot(int_escape(substr($name, 4))); - $source = $_POST['source'] ?? null; - [$image_ids, $errors] = $this->try_upload($file, $tags, $source); - $all_image_ids = array_merge($all_image_ids, $image_ids); - $all_errors = array_merge($all_errors, $errors); - } - foreach ($urls as $name => $value) { - $tags = $this->tags_for_upload_slot(int_escape(substr($name, 3))); - $source = $_POST['source'] ?? $value; - [$image_ids, $errors] = $this->try_transload($value, $tags, $source); - $all_image_ids = array_merge($all_image_ids, $image_ids); - $all_errors = array_merge($all_errors, $errors); - } + if($event->method == "GET") { + $this->theme->display_page($page); + } elseif($event->method == "POST") { + $all_image_ids = []; + $all_errors = []; + + $files = array_filter($_FILES, function ($file) { + return !empty($file['name']); + }); + foreach ($files as $name => $file) { + $slot = int_escape(substr($name, 4)); + $tags = $this->tags_for_upload_slot($slot); + $source = $this->source_for_upload_slot($slot); + [$image_ids, $errors] = $this->try_upload($file, $tags, $source); + $all_image_ids = array_merge($all_image_ids, $image_ids); + $all_errors = array_merge($all_errors, $errors); + } - if(!empty($_GET['url'])) { - $url = $_GET['url']; - $source = $_GET['source'] ?? $url; - $tags = ['tagme']; - if (!empty($_GET['tags']) && $_GET['tags'] != "null") { - $tags = Tag::explode($_GET['tags']); + $urls = array_filter($_POST, function ($value, $key) { + return str_starts_with($key, "url") && strlen($value) > 0; + }, ARRAY_FILTER_USE_BOTH); + foreach ($urls as $name => $value) { + $slot = int_escape(substr($name, 3)); + $tags = $this->tags_for_upload_slot($slot); + $source = $this->source_for_upload_slot($slot); + [$image_ids, $errors] = $this->try_transload($value, $tags, $source); + $all_image_ids = array_merge($all_image_ids, $image_ids); + $all_errors = array_merge($all_errors, $errors); } - [$image_ids, $errors] = $this->try_transload($url, $tags, $source); - $all_image_ids = array_merge($all_image_ids, $image_ids); - $all_errors = array_merge($all_errors, $errors); + $this->theme->display_upload_status($page, $all_image_ids, $all_errors); } - - $this->theme->display_upload_status($page, $all_image_ids, $all_errors); } } @@ -299,6 +277,11 @@ private function tags_for_upload_slot(int $id): array ); } + private function source_for_upload_slot(int $id): ?string + { + return $_POST["source$id"] ?? $_POST['source'] ?? null; + } + /** * Returns a descriptive error message for the specified PHP error code. * diff --git a/ext/upload/test.php b/ext/upload/test.php index 9e12f99e0a..be099ba6f6 100644 --- a/ext/upload/test.php +++ b/ext/upload/test.php @@ -67,7 +67,7 @@ public function testRawReplace() ] ]; - $page = $this->post_page("upload/replace", ["image_id" => $image_id]); + $page = $this->post_page("replace/$image_id"); $this->assert_response(302); $this->assertEquals("/test/post/view/$image_id", $page->redirect); diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 728b048c96..43a45fa506 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -52,15 +52,15 @@ public function display_page(Page $page): void ["id" => "large_upload_form", "class" => "vert"], TR( TD(["width" => "20"], rawHTML("Common Tags")), - TD(["colspan" => "5"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags", "autocomplete" => "off"])) + TD(["colspan" => "6"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags"])) ), TR( TD(["width" => "20"], rawHTML("Common Source")), - TD(["colspan" => "5"], INPUT(["name" => "source", "type" => "text"])) + TD(["colspan" => "6"], INPUT(["name" => "source", "type" => "text", "placeholder" => "https://..."])) ), $upload_list, TR( - TD(["colspan" => "6"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"])) + TD(["colspan" => "7"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"])) ), ) ); @@ -85,7 +85,7 @@ public function display_page(Page $page): void $page->add_block(new NavBlock()); $page->add_block(new Block("Upload", $html, "main", 20)); if ($tl_enabled) { - $page->add_block(new Block("Bookmarklets", (string)$this->h_bookmarklets(), "left", 20)); + $page->add_block(new Block("Bookmarklets", $this->build_bookmarklets(), "left", 20)); } } @@ -99,9 +99,10 @@ protected function build_upload_list(): HTMLElement $upload_list->appendChild( TR( - TD(["colspan" => $tl_enabled ? 2 : 4], "Files"), - $tl_enabled ? TD(["colspan" => "2"], "URLs") : emptyHTML(), - TD(["colspan" => "2"], "Post-Specific Tags"), + TD(["colspan" => 2], "Select File"), + TD($tl_enabled ? "or URL" : null), + TD("Post-Specific Tags"), + TD("Post-Specific Source"), ) ); @@ -109,12 +110,42 @@ protected function build_upload_list(): HTMLElement $upload_list->appendChild( TR( TD( - ["colspan" => $tl_enabled ? 2 : 4], - DIV(["id" => "canceldata{$i}", "style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;","onclick" => "document.getElementById('data{$i}').value='';updateTracker();"], "✖"), - INPUT(["type" => "file", "id"=>"data{$i}", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true]) + ["colspan" => 2, "style" => "white-space: nowrap;"], + DIV([ + "id" => "canceldata{$i}", + "style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;", + "onclick" => "document.getElementById('data{$i}').value='';updateTracker();", + ], "✖"), + INPUT([ + "type" => "file", + "id" => "data{$i}", + "name" => "data{$i}[]", + "accept" => $accept, + "multiple" => true, + ]), + ), + TD( + $tl_enabled ? INPUT([ + "type" => "text", + "name" => "url{$i}", + "value" => ($i == 0) ? @$_GET['url'] : null, + ]) : null + ), + TD( + INPUT([ + "type" => "text", + "name" => "tags{$i}", + "class" => "autocomplete_tags", + "value" => ($i == 0) ? @$_GET['tags'] : null, + ]) + ), + TD( + INPUT([ + "type" => "text", + "name" => "source{$i}", + "value" => ($i == 0) ? @$_GET['source'] : null, + ]) ), - $tl_enabled ? TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "url{$i}"])) : emptyHTML(), - TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "tags{$i}", "class" => "autocomplete_tags"])), ) ); } @@ -122,7 +153,7 @@ protected function build_upload_list(): HTMLElement return $upload_list; } - protected function h_bookmarklets(): HTMLElement + protected function build_bookmarklets(): HTMLElement { global $config; $link = make_http(make_link("upload")); @@ -197,7 +228,7 @@ public function display_replace_page(Page $page, int $image_id) $upload_list->appendChild( TR( TD("or URL"), - TD(INPUT(["name" => "url", "type" => "text"])) + TD(INPUT(["name" => "url", "type" => "text", "value" => @$_GET['url']])) ) ); } @@ -208,9 +239,8 @@ public function display_replace_page(Page $page, int $image_id) $image = Image::by_id($image_id); $thumbnail = $this->build_thumb_html($image); - $form = SHM_FORM("upload/replace/".$image_id, "POST", true); + $form = SHM_FORM("replace/".$image_id, "POST", true); $form->appendChild(emptyHTML( - INPUT(["type" => "hidden", "name" => "image_id", "value" => $image_id]), TABLE( ["id" => "large_upload_form", "class" => "vert"], $upload_list, diff --git a/index.php b/index.php index 5b7cbb9604..1cc8e2c02d 100644 --- a/index.php +++ b/index.php @@ -82,7 +82,7 @@ if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') { send_event(new CommandEvent($argv)); } else { - send_event(new PageRequestEvent(_get_query())); + send_event(new PageRequestEvent($_SERVER['REQUEST_METHOD'], _get_query())); $page->display(); } From b83d0d3c145818c00c68b7d9e6bbede4bc0d41ac Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 03:05:35 +0000 Subject: [PATCH 130/154] [devcontainer] add phpstan --- .devcontainer/devcontainer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 968d7a846f..11ca6759b0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,15 +14,13 @@ // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [8000], - // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", + "postStartCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", "containerEnv": { "UID": "2000", "GID": "2000", - "UPLOAD_MAX_FILESIZE": "50M", + "UPLOAD_MAX_FILESIZE": "50M" }, "customizations": { "vscode": { @@ -31,7 +29,8 @@ "ryanluker.vscode-coverage-gutters", "xdebug.php-debug", "DEVSENSE.phptools-vscode", - "ms-azuretools.vscode-docker" + "ms-azuretools.vscode-docker", + "SanderRonde.phpstan-vscode" ], "settings": { "phpunit.args": [ @@ -40,7 +39,8 @@ ], "coverage-gutters.coverageFileNames": [ "data/coverage.clover" - ] + ], + "phpstan.configFile": "tests/phpstan.neon" } } } From 9f002b6bf70574632f3cfc21039fe21d5d24efd4 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 03:27:39 +0000 Subject: [PATCH 131/154] typed arrays --- core/event.php | 2 +- ext/auto_tagger/main.php | 2 +- ext/comment/theme.php | 6 +++--- ext/ext_manager/main.php | 2 +- ext/ext_manager/theme.php | 2 +- ext/index/events.php | 2 +- ext/index/theme.php | 12 ++++++------ ext/not_a_tag/main.php | 4 ++-- ext/ouroboros_api/main.php | 2 +- ext/random_list/theme.php | 4 ++-- ext/rating/main.php | 2 +- ext/report_image/theme.php | 2 +- ext/tag_edit/main.php | 2 +- ext/tag_history/main.php | 2 +- ext/tag_list/main.php | 2 +- ext/upload/main.php | 4 ++-- themes/danbooru/index.theme.php | 4 ++-- themes/danbooru2/index.theme.php | 6 +++--- 18 files changed, 31 insertions(+), 31 deletions(-) diff --git a/core/event.php b/core/event.php index 20464d7cd9..5e8e9936d4 100644 --- a/core/event.php +++ b/core/event.php @@ -213,7 +213,7 @@ class CommandEvent extends Event public array $args = []; /** - * #param string[] $args + * @param string[] $args */ public function __construct(array $args) { diff --git a/ext/auto_tagger/main.php b/ext/auto_tagger/main.php index c9a4b707f1..fc2d860acb 100644 --- a/ext/auto_tagger/main.php +++ b/ext/auto_tagger/main.php @@ -272,7 +272,7 @@ private function remove_auto_tag(string $tag) } /** - * #param string[] $tags_mixed + * @param string[] $tags_mixed */ private function apply_auto_tags(array $tags_mixed): ?array { diff --git a/ext/comment/theme.php b/ext/comment/theme.php index 8bef60e0d7..911b4eb0b0 100644 --- a/ext/comment/theme.php +++ b/ext/comment/theme.php @@ -111,7 +111,7 @@ public function display_admin_block() /** * Add some comments to the page, probably in a sidebar. * - * #param Comment[] $comments An array of Comment objects to be shown + * @param Comment[] $comments An array of Comment objects to be shown */ public function display_recent_comments(array $comments) { @@ -128,7 +128,7 @@ public function display_recent_comments(array $comments) /** * Show comments for an image. * - * #param Comment[] $comments + * @param Comment[] $comments */ public function display_image_comments(Image $image, array $comments, bool $postbox) { @@ -147,7 +147,7 @@ public function display_image_comments(Image $image, array $comments, bool $post /** * Show comments made by a user. * - * #param Comment[] $comments + * @param Comment[] $comments */ public function display_recent_user_comments(array $comments, User $user) { diff --git a/ext/ext_manager/main.php b/ext/ext_manager/main.php index 164b0e8222..c66a3edd85 100644 --- a/ext/ext_manager/main.php +++ b/ext/ext_manager/main.php @@ -137,7 +137,7 @@ private function set_things($settings) } /** - * #param string[] $extras + * @param string[] $extras */ private function write_config(array $extras) { diff --git a/ext/ext_manager/theme.php b/ext/ext_manager/theme.php index 2b5bb35c20..8e0ee1fafc 100644 --- a/ext/ext_manager/theme.php +++ b/ext/ext_manager/theme.php @@ -25,7 +25,7 @@ class ExtManagerTheme extends Themelet { /** - * #param ExtensionInfo[] $extensions + * @param ExtensionInfo[] $extensions */ public function display_table(Page $page, array $extensions, bool $editable) { diff --git a/ext/index/events.php b/ext/index/events.php index f89f1eb759..0c9578722f 100644 --- a/ext/index/events.php +++ b/ext/index/events.php @@ -67,7 +67,7 @@ class PostListBuildingEvent extends Event public array $parts = []; /** - * #param string[] $search + * @param string[] $search */ public function __construct(array $search) { diff --git a/ext/index/theme.php b/ext/index/theme.php index 72294709e0..9932eb1fc2 100644 --- a/ext/index/theme.php +++ b/ext/index/theme.php @@ -42,7 +42,7 @@ public function display_intro(Page $page) } /** - * #param Image[] $images + * @param Image[] $images */ public function display_page(Page $page, array $images) { @@ -61,7 +61,7 @@ public function display_page(Page $page, array $images) } /** - * #param string[] $parts + * @param string[] $parts */ public function display_admin_block(array $parts) { @@ -71,7 +71,7 @@ public function display_admin_block(array $parts) /** - * #param string[] $search_terms + * @param string[] $search_terms */ protected function build_navigation(int $page_number, int $total_pages, array $search_terms): string { @@ -96,7 +96,7 @@ protected function build_navigation(int $page_number, int $total_pages, array $s } /** - * #param Image[] $images + * @param Image[] $images */ protected function build_table(array $images, ?string $query): string { @@ -139,7 +139,7 @@ protected function display_shortwiki(Page $page) } /** - * #param Image[] $images + * @param Image[] $images */ protected function display_page_header(Page $page, array $images) { @@ -165,7 +165,7 @@ protected function display_page_header(Page $page, array $images) } /** - * #param Image[] $images + * @param Image[] $images */ protected function display_page_images(Page $page, array $images) { diff --git a/ext/not_a_tag/main.php b/ext/not_a_tag/main.php index 7b1028d8b9..84ed8ee477 100644 --- a/ext/not_a_tag/main.php +++ b/ext/not_a_tag/main.php @@ -68,7 +68,7 @@ public function onTagSet(TagSetEvent $event) } /** - * #param string[] $tags_mixed + * @param string[] $tags_mixed */ private function scan(array $tags_mixed) { @@ -90,7 +90,7 @@ private function scan(array $tags_mixed) } /** - * #param string[] $tags + * @param string[] $tags */ private function strip(array $tags): array { diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index 70e9758e3d..93c3481471 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -415,7 +415,7 @@ protected function postShow(int $id = null) /** * Wrapper for getting a list of posts - * #param string[] $tags + * @param string[] $tags */ protected function postIndex(int $limit, int $page, array $tags) { diff --git a/ext/random_list/theme.php b/ext/random_list/theme.php index a0d2da4607..8ccd513429 100644 --- a/ext/random_list/theme.php +++ b/ext/random_list/theme.php @@ -9,7 +9,7 @@ class RandomListTheme extends Themelet protected array $search_terms; /** - * #param string[] $search_terms + * @param string[] $search_terms */ public function set_page(array $search_terms) { @@ -17,7 +17,7 @@ public function set_page(array $search_terms) } /** - * #param Image[] $images + * @param Image[] $images */ public function display_page(Page $page, array $images) { diff --git a/ext/rating/main.php b/ext/rating/main.php index 34a1e4a604..05363c0d0f 100644 --- a/ext/rating/main.php +++ b/ext/rating/main.php @@ -485,7 +485,7 @@ public static function rating_is_valid(string $rating): bool } /** - * #param string[] $context + * @param string[] $context */ private function no_rating_query(array $context): bool { diff --git a/ext/report_image/theme.php b/ext/report_image/theme.php index 55a16b9993..91af0b05fc 100644 --- a/ext/report_image/theme.php +++ b/ext/report_image/theme.php @@ -56,7 +56,7 @@ public function display_reported_images(Page $page, array $reports) } /** - * #param ImageReport[] $reports + * @param ImageReport[] $reports */ public function display_image_banner(Image $image, array $reports) { diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php index 3e49ee27bd..309c4ae7f7 100644 --- a/ext/tag_edit/main.php +++ b/ext/tag_edit/main.php @@ -56,7 +56,7 @@ class TagSetEvent extends Event public array $metatags; /** - * #param string[] $tags + * @param string[] $tags */ public function __construct(Image $image, array $tags) { diff --git a/ext/tag_history/main.php b/ext/tag_history/main.php index a748a55204..94da23e5f7 100644 --- a/ext/tag_history/main.php +++ b/ext/tag_history/main.php @@ -346,7 +346,7 @@ public function process_revert_all_changes(?string $name, ?string $ip, ?string $ /** * This function is called just before an images tag are changed. * - * #param string[] $tags + * @param string[] $tags */ private function add_tag_history(Image $image, array $tags) { diff --git a/ext/tag_list/main.php b/ext/tag_list/main.php index 89d451389b..97bd15460d 100644 --- a/ext/tag_list/main.php +++ b/ext/tag_list/main.php @@ -530,7 +530,7 @@ private function add_popular_block(Page $page) } /** - * #param string[] $search + * @param string[] $search */ private function add_refine_block(Page $page, array $search) { diff --git a/ext/upload/main.php b/ext/upload/main.php index 46bec523c0..96a234674c 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -314,8 +314,8 @@ private function upload_error_message(int $error_code): string /** * Handle an upload. - * #param string[] $file - * #param string[] $tags + * @param mixed[] $file + * @param string[] $tags */ private function try_upload(array $file, array $tags, ?string $source = null, ?int $replace_id = null): array { diff --git a/themes/danbooru/index.theme.php b/themes/danbooru/index.theme.php index 20e32b386f..8dd0aa9ced 100644 --- a/themes/danbooru/index.theme.php +++ b/themes/danbooru/index.theme.php @@ -7,7 +7,7 @@ class CustomIndexTheme extends IndexTheme { /** - * #param Image[] $images + * @param Image[] $images */ public function display_page(Page $page, array $images) { @@ -42,7 +42,7 @@ public function display_page(Page $page, array $images) } /** - * #param string[] $search_terms + * @param string[] $search_terms */ protected function build_navigation(int $page_number, int $total_pages, array $search_terms): string { diff --git a/themes/danbooru2/index.theme.php b/themes/danbooru2/index.theme.php index 51bf447308..e1fa8aa47b 100644 --- a/themes/danbooru2/index.theme.php +++ b/themes/danbooru2/index.theme.php @@ -7,7 +7,7 @@ class CustomIndexTheme extends IndexTheme { /** - * #param Image[] $images + * @param Image[] $images */ public function display_page(Page $page, array $images) { @@ -26,7 +26,7 @@ public function display_page(Page $page, array $images) } /** - * #param string[] $search_terms + * @param string[] $search_terms */ protected function build_navigation(int $page_number, int $total_pages, array $search_terms): string { @@ -42,7 +42,7 @@ protected function build_navigation(int $page_number, int $total_pages, array $s } /** - * #param Image[] $images + * @param Image[] $images */ protected function build_table(array $images, ?string $query): string { From c0d9946e71c51382c8ad4105593e833e9c106919 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 04:10:38 +0000 Subject: [PATCH 132/154] [upload] go full OOP for upload results --- ext/image/main.php | 8 +-- ext/upload/main.php | 119 +++++++++++++++++++++++-------------------- ext/upload/theme.php | 22 ++++---- 3 files changed, 78 insertions(+), 71 deletions(-) diff --git a/ext/image/main.php b/ext/image/main.php index e361f93a7d..a0acad9337 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -152,9 +152,7 @@ public function onImageAddition(ImageAdditionEvent $event) $event->image = $im; return; } else { - $error = "Post {$existing->id} ". - "already has hash {$image->hash}:

        ".$this->theme->build_thumb_html($existing); - throw new ImageAdditionException($error); + throw new ImageAdditionException(">>{$existing->id} already has hash {$image->hash}"); } } @@ -216,9 +214,7 @@ public function onImageReplace(ImageReplaceEvent $event) $duplicate = Image::by_hash($image->hash); if (!is_null($duplicate) && $duplicate->id != $id) { - $error = "Post {$duplicate->id} " . - "already has hash {$image->hash}:

        " . $this->theme->build_thumb_html($duplicate); - throw new ImageReplaceException($error); + throw new ImageReplaceException(">>{$duplicate->id} already has hash {$image->hash}"); } if (strlen(trim($image->source ?? '')) == 0) { diff --git a/ext/upload/main.php b/ext/upload/main.php index 96a234674c..bc53e1d05e 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -58,12 +58,31 @@ class UploadException extends SCoreException { } -class UploadError +abstract class UploadResult { public function __construct( - public string $name, + public string $name + ) { + } +} + +class UploadError extends UploadResult +{ + public function __construct( + string $name, public string $error ) { + parent::__construct($name); + } +} + +class UploadSuccess extends UploadResult +{ + public function __construct( + string $name, + public int $image_id + ) { + parent::__construct($name); } } @@ -211,15 +230,14 @@ public function onPageRequest(PageRequestEvent $event) if($event->method == "GET") { $this->theme->display_replace_page($page, $image_id); } elseif($event->method == "POST") { - $image_ids = []; - $errors = []; + $results = []; if (!empty($_POST["url"])) { - [$image_ids, $errors] = $this->try_transload($_POST["url"], [], $_POST['source'] ?? null, $image_id); + $results = $this->try_transload($_POST["url"], [], $_POST['source'] ?? null, $image_id); } elseif (count($_FILES) > 0) { - [$image_ids, $errors] = $this->try_upload($_FILES["data"], [], $_POST['source'] ?? null, $image_id); + $results = $this->try_upload($_FILES["data"], [], $_POST['source'] ?? null, $image_id); } $cache->delete("thumb-block:{$image_id}"); - $this->theme->display_upload_status($page, $image_ids, $errors); + $this->theme->display_upload_status($page, $results); } } elseif ($event->page_matches("upload")) { if (!$user->can(Permissions::CREATE_IMAGE)) { @@ -234,8 +252,7 @@ public function onPageRequest(PageRequestEvent $event) if($event->method == "GET") { $this->theme->display_page($page); } elseif($event->method == "POST") { - $all_image_ids = []; - $all_errors = []; + $results = []; $files = array_filter($_FILES, function ($file) { return !empty($file['name']); @@ -244,9 +261,7 @@ public function onPageRequest(PageRequestEvent $event) $slot = int_escape(substr($name, 4)); $tags = $this->tags_for_upload_slot($slot); $source = $this->source_for_upload_slot($slot); - [$image_ids, $errors] = $this->try_upload($file, $tags, $source); - $all_image_ids = array_merge($all_image_ids, $image_ids); - $all_errors = array_merge($all_errors, $errors); + $results = array_merge($results, $this->try_upload($file, $tags, $source)); } $urls = array_filter($_POST, function ($value, $key) { @@ -256,12 +271,10 @@ public function onPageRequest(PageRequestEvent $event) $slot = int_escape(substr($name, 3)); $tags = $this->tags_for_upload_slot($slot); $source = $this->source_for_upload_slot($slot); - [$image_ids, $errors] = $this->try_transload($value, $tags, $source); - $all_image_ids = array_merge($all_image_ids, $image_ids); - $all_errors = array_merge($all_errors, $errors); + $results = array_merge($results, $this->try_transload($value, $tags, $source)); } - $this->theme->display_upload_status($page, $all_image_ids, $all_errors); + $this->theme->display_upload_status($page, $results); } } } @@ -316,6 +329,7 @@ private function upload_error_message(int $error_code): string * Handle an upload. * @param mixed[] $file * @param string[] $tags + * @return UploadResult[] */ private function try_upload(array $file, array $tags, ?string $source = null, ?int $replace_id = null): array { @@ -330,55 +344,50 @@ private function try_upload(array $file, array $tags, ?string $source = null, ?i $source = null; } - $image_ids = []; - $errors = []; + $results = []; - $num_files = count($file['name']); - $limit = $config->get_int(UploadConfig::COUNT); - try { - if ($num_files > $limit) { - throw new UploadException("Upload limited to $limit files at a time"); - } + for ($i = 0; $i < count($file['name']); $i++) { + $name = $file['name'][$i]; + $error = $file['error'][$i]; + $tmp_name = $file['tmp_name'][$i]; - for ($i = 0; $i < $num_files; $i++) { - if (empty($file['name'][$i])) { - continue; + if (empty($name)) { + continue; + } + try { + // check if the upload was successful + if ($error !== UPLOAD_ERR_OK) { + throw new UploadException($this->upload_error_message($error)); } - try { - // check if the upload was successful - if ($file['error'][$i] !== UPLOAD_ERR_OK) { - throw new UploadException($this->upload_error_message($file['error'][$i])); - } - - $metadata = []; - $metadata['filename'] = pathinfo($file['name'][$i], PATHINFO_BASENAME); - $metadata['tags'] = $tags; - $metadata['source'] = $source; - - $event = new DataUploadEvent($file['tmp_name'][$i], $metadata, $replace_id); - send_event($event); - if ($event->image_id == -1) { - throw new UploadException("MIME type not supported: " . $event->mime); - } - $image_ids[] = $event->image_id; - $page->add_http_header("X-Shimmie-Post-ID: " . $event->image_id); - } catch (UploadException $ex) { - $errors[] = new UploadError($file['name'][$i], $ex->getMessage()); + + $metadata = []; + $metadata['filename'] = pathinfo($name, PATHINFO_BASENAME); + $metadata['tags'] = $tags; + $metadata['source'] = $source; + + $event = new DataUploadEvent($tmp_name, $metadata, $replace_id); + send_event($event); + if ($event->image_id == -1) { + throw new UploadException("MIME type not supported: " . $event->mime); } + $results[] = new UploadSuccess($name, $event->image_id); + $page->add_http_header("X-Shimmie-Post-ID: " . $event->image_id); + } catch (UploadException $ex) { + $results[] = new UploadError($name, $ex->getMessage()); } - } catch (UploadException $ex) { - $errors[] = new UploadError('unknown', $ex->getMessage()); } - return [$image_ids, $errors]; + return $results; } + /** + * @return UploadResult[] + */ private function try_transload(string $url, array $tags, string $source = null, ?int $replace_id = null): array { global $page, $config, $user; - $image_ids = []; - $errors = []; + $results = []; $tmp_filename = tempnam(ini_get('upload_tmp_dir'), "shimmie_transload"); try { @@ -415,15 +424,15 @@ private function try_transload(string $url, array $tags, string $source = null, if ($event->image_id == -1) { throw new UploadException("File type not supported: " . $event->mime); } - $image_ids[] = $event->image_id; + $results[] = new UploadSuccess($url, $event->image_id); } catch (UploadException $ex) { - $errors[] = new UploadError($url, $ex->getMessage()); + $results[] = new UploadError($url, $ex->getMessage()); } finally { if (file_exists($tmp_filename)) { unlink($tmp_filename); } } - return [$image_ids, $errors]; + return $results; } } diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 43a45fa506..fd7ca13048 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -268,31 +268,33 @@ public function display_replace_page(Page $page, int $image_id) } /** - * @param int[] $image_ids - * @param UploadError[] $errors + * @param UploadResult[] $results */ - public function display_upload_status(Page $page, array $image_ids, array $errors): void + public function display_upload_status(Page $page, array $results): void { global $user; + /** @var UploadSuccess[] */ + $successes = array_filter($results, fn ($r) => is_a($r, UploadSuccess::class)); + + /** @var UploadError[] */ + $errors = array_filter($results, fn ($r) => is_a($r, UploadError::class)); + if (count($errors) > 0) { $page->set_title("Upload Status"); $page->set_heading("Upload Status"); $page->add_block(new NavBlock()); foreach($errors as $error) { - $message = $error->error; - // this message has intentional HTML in it... - $message = str_contains($message, "already has hash") ? $message : html_escape($message); - $page->add_block(new Block($error->name, $message)); + $page->add_block(new Block($error->name, format_text($error->error))); } - } elseif (count($image_ids) == 0) { + } elseif (count($successes) == 0) { $page->set_title("No images uploaded"); $page->set_heading("No images uploaded"); $page->add_block(new NavBlock()); $page->add_block(new Block("No images uploaded", "Upload attempted, but nothing succeeded and nothing failed?")); - } elseif (count($image_ids) == 1) { + } elseif (count($successes) == 1) { $page->set_mode(PageMode::REDIRECT); - $page->set_redirect(make_link("post/view/{$image_ids[0]}")); + $page->set_redirect(make_link("post/view/{$successes[0]->image_id}")); } else { $page->set_mode(PageMode::REDIRECT); $page->set_redirect(search_link(["poster={$user->name}"])); From 5dc7bf6907a90d693edeba275b910a6481113c86 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 12:03:13 +0000 Subject: [PATCH 133/154] Revert "[devcontainer] add phpstan" This reverts commit b83d0d3c145818c00c68b7d9e6bbede4bc0d41ac. --- .devcontainer/devcontainer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 11ca6759b0..968d7a846f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,13 +14,15 @@ // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [8000], - "postStartCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", + // Uncomment the next line to run commands after the container is created. + "postCreateCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", "containerEnv": { "UID": "2000", "GID": "2000", - "UPLOAD_MAX_FILESIZE": "50M" + "UPLOAD_MAX_FILESIZE": "50M", }, "customizations": { "vscode": { @@ -29,8 +31,7 @@ "ryanluker.vscode-coverage-gutters", "xdebug.php-debug", "DEVSENSE.phptools-vscode", - "ms-azuretools.vscode-docker", - "SanderRonde.phpstan-vscode" + "ms-azuretools.vscode-docker" ], "settings": { "phpunit.args": [ @@ -39,8 +40,7 @@ ], "coverage-gutters.coverageFileNames": [ "data/coverage.clover" - ], - "phpstan.configFile": "tests/phpstan.neon" + ] } } } From 87fea055b18303e66c006249d4a14b09c92c6713 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 14:59:26 +0000 Subject: [PATCH 134/154] bumps --- composer.json | 2 +- composer.lock | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index da06083655..17a618d348 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "ifixit/php-akismet" : "^1.0", "google/recaptcha" : "^1.1", "shish/eventtracer-php" : "^2.0", - "shish/ffsphp" : "^1.0", + "shish/ffsphp" : "^1.3", "shish/microcrud" : "^2.0", "shish/microhtml" : "^2.2", "shish/gqla" : "dev-main", diff --git a/composer.lock b/composer.lock index 74d150694b..c128092a24 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "091b1bcf581b4d04a9989860b068f327", + "content-hash": "0e61e4e500633d5c596af3b192df7045", "packages": [ { "name": "aws/aws-crt-php", @@ -1580,16 +1580,16 @@ }, { "name": "shish/ffsphp", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/shish/ffsphp.git", - "reference": "a2a1f0fe59606b90460513b48bacb442630dd253" + "reference": "26eea8149fda5f20bed7399b8b4a84946448bec0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shish/ffsphp/zipball/a2a1f0fe59606b90460513b48bacb442630dd253", - "reference": "a2a1f0fe59606b90460513b48bacb442630dd253", + "url": "https://api.github.com/repos/shish/ffsphp/zipball/26eea8149fda5f20bed7399b8b4a84946448bec0", + "reference": "26eea8149fda5f20bed7399b8b4a84946448bec0", "shasum": "" }, "require": { @@ -1597,9 +1597,9 @@ "php": "^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.12", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.0" + "friendsofphp/php-cs-fixer": "3.41.1", + "phpstan/phpstan": "1.10.50", + "phpunit/phpunit": "10.5.3" }, "type": "library", "autoload": { @@ -1623,9 +1623,9 @@ "homepage": "https://github.com/shish/ffsphp", "support": { "issues": "https://github.com/shish/ffsphp/issues", - "source": "https://github.com/shish/ffsphp/tree/v1.2.0" + "source": "https://github.com/shish/ffsphp/tree/v1.3.0" }, - "time": "2023-08-28T15:51:16+00:00" + "time": "2024-01-01T14:48:00+00:00" }, { "name": "shish/gqla", @@ -6059,5 +6059,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From 7563576557db50c78e998198461c6503b0c2240a Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 15:48:33 +0000 Subject: [PATCH 135/154] [docker] allow source maps to be downloaded --- .docker/entrypoint.d/config.json.tmpl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.docker/entrypoint.d/config.json.tmpl b/.docker/entrypoint.d/config.json.tmpl index ee37c21d21..d1088d6efe 100644 --- a/.docker/entrypoint.d/config.json.tmpl +++ b/.docker/entrypoint.d/config.json.tmpl @@ -35,6 +35,7 @@ "image/*", "application/javascript", "text/css", + "application/sourcemap", "!" ], "response_headers": { @@ -69,7 +70,12 @@ }, "settings": { "http": { - "max_body_size": 1048576000 + "max_body_size": 1048576000, + "static": { + "mime_types": { + "application/sourcemap": [".map"] + } + } } } } From f611f9b1e48dec6971d947cd0d5d32160487d6c6 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 16:05:48 +0000 Subject: [PATCH 136/154] [bbcode] make sure bbcode css only applies to bbcode --- ext/bbcode/main.php | 8 +++++++- ext/bbcode/style.css | 13 ++++++------- ext/bbcode/test.php | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/ext/bbcode/main.php b/ext/bbcode/main.php index 5d39b6f45c..8746a0504f 100644 --- a/ext/bbcode/main.php +++ b/ext/bbcode/main.php @@ -7,6 +7,12 @@ class BBCode extends FormatterExtension { public function format(string $text): string + { + $text = $this->_format($text); + return "$text"; + } + + public function _format(string $text): string { $text = $this->extract_code($text); foreach ([ @@ -158,7 +164,7 @@ private function insert_code(string $text): string $middle = base64_decode(substr($text, $start + $l1, ($end - $start - $l1))); $ending = substr($text, $end + $l2, (strlen($text) - $end + $l2)); - $text = $beginning . "

        " . $middle . "
        " . $ending; + $text = $beginning . "
        " . $middle . "
        " . $ending; } return $text; } diff --git a/ext/bbcode/style.css b/ext/bbcode/style.css index 32c257a12b..525ff5c54e 100644 --- a/ext/bbcode/style.css +++ b/ext/bbcode/style.css @@ -1,16 +1,15 @@ - -CODE { - background: #DEDEDE; - font-size: 0.8rem; +.bbcode PRE.code { + background: #DEDEDE; + font-size: 0.9rem; } -BLOCKQUOTE { +.bbcode BLOCKQUOTE { border: 1px solid black; padding: 8px; background: #DDD; } -.anchor A.alink { +.bbcode .anchor A.alink { visibility: hidden; } -.anchor:hover A.alink { +.bbcode .anchor:hover A.alink { visibility: visible; } diff --git a/ext/bbcode/test.php b/ext/bbcode/test.php index 359ee3e04a..20b42b6aa9 100644 --- a/ext/bbcode/test.php +++ b/ext/bbcode/test.php @@ -37,7 +37,7 @@ public function testFailure() public function testCode() { $this->assertEquals( - "
        [b]bold[/b]
        ", + "
        [b]bold[/b]
        ", $this->filter("[code][b]bold[/b][/code]") ); } @@ -104,7 +104,7 @@ public function testAnchor() private function filter($in): string { $bb = new BBCode(); - return $bb->format($in); + return $bb->_format($in); } private function strip($in): string From 585ba7f42d03ee7349b1af0a8ca887056c0e3604 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 16:07:59 +0000 Subject: [PATCH 137/154] [autocomplete] remove dead css --- ext/autocomplete/style.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ext/autocomplete/style.css b/ext/autocomplete/style.css index e00f20f09a..e258e62e6b 100644 --- a/ext/autocomplete/style.css +++ b/ext/autocomplete/style.css @@ -17,8 +17,3 @@ background-color: #ccc; outline: none; } -input[name=search] ~ input[type=submit] { display: inline-block !important; } - -.tag-negative { background: #ff8080 !important; } -.tag-positive { background: #40bf40 !important; } -.tag-metatag { background: #eaa338 !important; } From 7264683a040192f6e92b878aa0902073604f7c7e Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 16:10:26 +0000 Subject: [PATCH 138/154] [home] make sure home css only applies to home page --- ext/home/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/home/style.css b/ext/home/style.css index c1c9b78a5d..9bc9ecc938 100644 --- a/ext/home/style.css +++ b/ext/home/style.css @@ -1,11 +1,11 @@ div#front-page h1 {font-size: 4rem; margin-top: 2em; margin-bottom: 0; text-align: center; border: none; background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;} div#front-page {text-align:center;} -.space {margin-bottom: 1em;} +div#front-page .space {margin-bottom: 1em;} div#front-page div#links a {margin: 0 0.5em;} div#front-page li {list-style-type: none; margin: 0;} @media (max-width: 800px) { div#front-page h1 {font-size: 3rem; margin-top: 0.5em; margin-bottom: 0.5em;} - #counter {display: none;} + div#front-page #counter {display: none;} } div#front-page > #search > form { margin: 0 auto; } From 3acc3fa6074bcc93dc5147a9b14434562e34e1f2 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 16:21:43 +0000 Subject: [PATCH 139/154] [css] remove more dead css, make more extension CSS be specific --- ext/bulk_actions/script.js | 4 ++-- ext/bulk_actions/style.css | 2 +- ext/cron_uploader/style.css | 2 +- ext/cron_uploader/theme.php | 2 +- ext/index/style.css | 2 -- ext/setup/style.css | 11 ----------- ext/tag_editcloud/style.css | 2 +- ext/upload/style.css | 15 +++------------ ext/upload/theme.php | 4 ++-- themes/danbooru/style.css | 15 --------------- themes/danbooru2/style.css | 15 --------------- themes/lite/style.css | 10 ---------- 12 files changed, 11 insertions(+), 73 deletions(-) diff --git a/ext/bulk_actions/script.js b/ext/bulk_actions/script.js index 831c008700..184a4b8cbd 100644 --- a/ext/bulk_actions/script.js +++ b/ext/bulk_actions/script.js @@ -68,11 +68,11 @@ function get_selected_items() { } function set_selected_items(items) { - $(".shm-thumb").removeClass('selected'); + $(".shm-thumb").removeClass('bulk_selected'); $(items).each( function(index,item) { - $('.shm-thumb[data-post-id="' + item + '"]').addClass('selected'); + $('.shm-thumb[data-post-id="' + item + '"]').addClass('bulk_selected'); } ); diff --git a/ext/bulk_actions/style.css b/ext/bulk_actions/style.css index 4e7449fc64..117a5ce870 100644 --- a/ext/bulk_actions/style.css +++ b/ext/bulk_actions/style.css @@ -1,4 +1,4 @@ -.selected { +.bulk_selected { outline: 3px solid blue; } diff --git a/ext/cron_uploader/style.css b/ext/cron_uploader/style.css index 2643a6a16f..770152bde3 100644 --- a/ext/cron_uploader/style.css +++ b/ext/cron_uploader/style.css @@ -1,3 +1,3 @@ -table.log th { +table.cron_uploader_log th { width: 200px; } \ No newline at end of file diff --git a/ext/cron_uploader/theme.php b/ext/cron_uploader/theme.php index e367ba195f..977f40f343 100644 --- a/ext/cron_uploader/theme.php +++ b/ext/cron_uploader/theme.php @@ -115,7 +115,7 @@ public function display_documentation( $page->add_block($block_usage); if (!empty($log_entries)) { - $log_html = ""; + $log_html = "
        "; foreach ($log_entries as $entry) { $log_html .= ""; } diff --git a/ext/index/style.css b/ext/index/style.css index 5ecd5a2827..8fdee55d7b 100644 --- a/ext/index/style.css +++ b/ext/index/style.css @@ -1,5 +1,3 @@ - -/*noinspection CssRedundantUnit*/ #image-list .blockbody { background: none; border: none; diff --git a/ext/setup/style.css b/ext/setup/style.css index 6237cd6501..c8904a5b16 100644 --- a/ext/setup/style.css +++ b/ext/setup/style.css @@ -17,17 +17,6 @@ resize: vertical; } -.helpable { - border-bottom: 1px dashed gray; -} - -.ok { - background: #AFA; -} -.bad { - background: #FAA; -} - #Setupmain .blockbody { background: none; border: none; diff --git a/ext/tag_editcloud/style.css b/ext/tag_editcloud/style.css index 0c5b9566d5..995d2d56a7 100644 --- a/ext/tag_editcloud/style.css +++ b/ext/tag_editcloud/style.css @@ -1,4 +1,4 @@ -span.tag-selected { +.tageditcloud span.tag-selected { background:#88EE88; } diff --git a/ext/upload/style.css b/ext/upload/style.css index d6d8038492..6dd19550d7 100644 --- a/ext/upload/style.css +++ b/ext/upload/style.css @@ -1,17 +1,8 @@ -/* Only need to change the file/url inputs */ -#large_upload_form INPUT.wid { - width: 100%; -} -#radio_button { - width: auto; -} -#wrapper { - opacity: 0.4; - filter: alpha(opacity=40); /* msie */ +#large_upload_form TD, +#large_upload_form TH { + vertical-align: middle; } -/* This is needed since the theme style.css forcibly sets vertical align to "top". */ -TABLE.vert TD, TABLE.vert TH {vertical-align: middle;} .mini_upload INPUT { width: 100%; } diff --git a/ext/upload/theme.php b/ext/upload/theme.php index fd7ca13048..78ee4005ad 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -49,7 +49,7 @@ public function display_page(Page $page): void $form = SHM_FORM("upload", "POST", true, "file_upload"); $form->appendChild( TABLE( - ["id" => "large_upload_form", "class" => "vert"], + ["id" => "large_upload_form"], TR( TD(["width" => "20"], rawHTML("Common Tags")), TD(["colspan" => "6"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags"])) @@ -242,7 +242,7 @@ public function display_replace_page(Page $page, int $image_id) $form = SHM_FORM("replace/".$image_id, "POST", true); $form->appendChild(emptyHTML( TABLE( - ["id" => "large_upload_form", "class" => "vert"], + ["id" => "large_upload_form"], $upload_list, TR(TD("Source"), TD(["colspan" => 3], INPUT(["name" => "source", "type" => "text"]))), TR(TD(["colspan" => 4], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"]))), diff --git a/themes/danbooru/style.css b/themes/danbooru/style.css index 598c05f91e..b912670b45 100644 --- a/themes/danbooru/style.css +++ b/themes/danbooru/style.css @@ -219,21 +219,6 @@ margin:16px; padding:8px; width:350px; } -.helpable { -border-bottom:1px dashed gray; -} -.ok { --moz-background-clip:border; --moz-background-inline-policy:continuous; --moz-background-origin:padding; -background:#AAFFAA none repeat scroll 0 0; -} -.bad { --moz-background-clip:border; --moz-background-inline-policy:continuous; --moz-background-origin:padding; -background:#FFAAAA none repeat scroll 0 0; -} .comment .username { font-size:1.5em; font-weight:bold; diff --git a/themes/danbooru2/style.css b/themes/danbooru2/style.css index ba42886f17..70d23acd26 100644 --- a/themes/danbooru2/style.css +++ b/themes/danbooru2/style.css @@ -269,21 +269,6 @@ width:100%; margin-top:0.4rem; padding:0.2rem 0.6rem; } -.helpable { -border-bottom:1px dashed gray; -} -.ok { -background:#AAFFAA none repeat scroll 0 0; --moz-background-clip:border; --moz-background-inline-policy:continuous; --moz-background-origin:padding; -} -.bad { -background:#FFAAAA none repeat scroll 0 0; --moz-background-clip:border; --moz-background-inline-policy:continuous; --moz-background-origin:padding; -} .comment .username { font-weight:bold; font-size:1.5em; diff --git a/themes/lite/style.css b/themes/lite/style.css index 7ee8564513..6ebe6fd4d1 100644 --- a/themes/lite/style.css +++ b/themes/lite/style.css @@ -316,16 +316,6 @@ ARTICLE TABLE { font-size: 0.75rem; } -.helpable { - border-bottom: 1px dashed gray; -} - -.ok { - background: #AFA; -} -.bad { - background: #FAA; -} NAV .thumbblock { float: none; From 47dfc2c36ecbc550b7fcc0ff35a0221ee26bd95b Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 17:17:27 +0000 Subject: [PATCH 140/154] fix view js a bit --- ext/view/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/view/script.js b/ext/view/script.js index 156a224bb3..ed459c0128 100644 --- a/ext/view/script.js +++ b/ext/view/script.js @@ -15,7 +15,7 @@ function clearViewMode() { function updateAttr(selector, attr, value) { document.querySelectorAll(selector).forEach(function(e) { let current = e.getAttribute(attr); - let newval = joinUrlSegments(current, query); + let newval = joinUrlSegments(current, value); e.setAttribute(attr, newval); }); } From 8815b702c5c90d3451d160394d5cc11e23167bb0 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 15:00:54 +0000 Subject: [PATCH 141/154] [core] DIY CSS/JS minifier --- composer.json | 36 +-- composer.lock | 773 ++++++++++++++-------------------------------- core/basepage.php | 33 +- 3 files changed, 258 insertions(+), 584 deletions(-) diff --git a/composer.json b/composer.json index 17a618d348..deadf44966 100644 --- a/composer.json +++ b/composer.json @@ -27,10 +27,6 @@ "reference" : "fd4ff50eb577457c1b7b887401663e91e77625ae" } } - }, - { - "type" : "vcs", - "url" : "https://github.com/shish/php-css.git" } ], @@ -39,24 +35,22 @@ "ext-pdo": "*", "ext-json": "*", "ext-fileinfo": "*", - - "flexihash/flexihash" : "^2.0", - "ifixit/php-akismet" : "^1.0", - "google/recaptcha" : "^1.1", - "shish/eventtracer-php" : "^2.0", - "shish/ffsphp" : "^1.3", - "shish/microcrud" : "^2.0", - "shish/microhtml" : "^2.2", - "shish/gqla" : "dev-main", - "enshrined/svg-sanitize" : "^0.16", - - "bower-asset/jquery" : "^1.12", - "bower-asset/jquery-timeago" : "^1.5", - "bower-asset/js-cookie" : "^2.1", - "psr/simple-cache" : "^1.0", - "sabre/cache" : "^2.0.1", + "flexihash/flexihash": "^2.0", + "ifixit/php-akismet": "^1.0", + "google/recaptcha": "^1.1", + "shish/eventtracer-php": "^2.0", + "shish/ffsphp": "^1.3", + "shish/microbundler": "^1.0", + "shish/microcrud": "^2.0", + "shish/microhtml": "^2.2", + "shish/gqla": "dev-main", + "enshrined/svg-sanitize": "^0.16", + "bower-asset/jquery": "^1.12", + "bower-asset/jquery-timeago": "^1.5", + "bower-asset/js-cookie": "^2.1", + "psr/simple-cache": "^1.0", + "sabre/cache": "^2.0.1", "naroga/redis-cache": "dev-master", - "tbela99/css": "dev-master", "aws/aws-sdk-php": "^3.294" }, diff --git a/composer.lock b/composer.lock index c128092a24..dcd6e1b67e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0e61e4e500633d5c596af3b192df7045", + "content-hash": "f414c6453498abc6d6578416a464b7e9", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.294.4", + "version": "3.295.4", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "4f59bf50aa445fc3ec0b10648b205dd2465e9bec" + "reference": "2372661db989fe4229abd95f4434b37252076d58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4f59bf50aa445fc3ec0b10648b205dd2465e9bec", - "reference": "4f59bf50aa445fc3ec0b10648b205dd2465e9bec", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2372661db989fe4229abd95f4434b37252076d58", + "reference": "2372661db989fe4229abd95f4434b37252076d58", "shasum": "" }, "require": { @@ -151,208 +151,16 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.294.4" + "source": "https://github.com/aws/aws-sdk-php/tree/3.295.4" }, - "time": "2023-12-20T19:21:19+00:00" - }, - { - "name": "axy/backtrace", - "version": "1.0.7", - "source": { - "type": "git", - "url": "https://github.com/axypro/backtrace.git", - "reference": "c6c7d0f3497a07ae934f9e8511cbc2286db311c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/axypro/backtrace/zipball/c6c7d0f3497a07ae934f9e8511cbc2286db311c5", - "reference": "c6c7d0f3497a07ae934f9e8511cbc2286db311c5", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "axy\\backtrace\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oleg Grigoriev", - "email": "go.vasac@gmail.com" - } - ], - "description": "Tracing in PHP", - "homepage": "https://github.com/axypro/backtrace", - "keywords": [ - "Backtrace", - "debug", - "exception", - "trace" - ], - "support": { - "issues": "https://github.com/axypro/backtrace/issues", - "source": "https://github.com/axypro/backtrace/tree/1.0.7" - }, - "time": "2019-02-02T15:52:44+00:00" - }, - { - "name": "axy/codecs-base64vlq", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/axypro/codecs-base64vlq.git", - "reference": "53a1957f2cb773c6533ac615b3f1ac59e40e13cc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/axypro/codecs-base64vlq/zipball/53a1957f2cb773c6533ac615b3f1ac59e40e13cc", - "reference": "53a1957f2cb773c6533ac615b3f1ac59e40e13cc", - "shasum": "" - }, - "require": { - "axy/errors": "~1.0.1", - "php": ">=5.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "axy\\codecs\\base64vlq\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oleg Grigoriev", - "email": "go.vasac@gmail.com" - } - ], - "description": "Codec for VLQ (variable-length quantity) Base64 algorithm", - "homepage": "https://github.com/axypro/codecs-base64vlq", - "keywords": [ - "Source map", - "VLQ", - "Variable length quantity", - "base64", - "codec" - ], - "support": { - "issues": "https://github.com/axypro/codecs-base64vlq/issues", - "source": "https://github.com/axypro/codecs-base64vlq/tree/master" - }, - "time": "2015-11-23T07:08:52+00:00" - }, - { - "name": "axy/errors", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/axypro/errors.git", - "reference": "2c64374ae2b9ca51304c09b6b6acc275557fc34f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/axypro/errors/zipball/2c64374ae2b9ca51304c09b6b6acc275557fc34f", - "reference": "2c64374ae2b9ca51304c09b6b6acc275557fc34f", - "shasum": "" - }, - "require": { - "axy/backtrace": "~1.0.2", - "php": ">=5.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "axy\\errors\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oleg Grigoriev", - "email": "go.vasac@gmail.com" - } - ], - "description": "Exceptions in PHP", - "homepage": "https://github.com/axypro/errors", - "keywords": [ - "error", - "exception" - ], - "support": { - "issues": "https://github.com/axypro/errors/issues", - "source": "https://github.com/axypro/errors/tree/1.0.5" - }, - "time": "2019-02-02T18:26:18+00:00" - }, - { - "name": "axy/sourcemap", - "version": "0.1.5", - "source": { - "type": "git", - "url": "https://github.com/axypro/sourcemap.git", - "reference": "95a52df5a08c3a011031dae2e79390134e28467c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/axypro/sourcemap/zipball/95a52df5a08c3a011031dae2e79390134e28467c", - "reference": "95a52df5a08c3a011031dae2e79390134e28467c", - "shasum": "" - }, - "require": { - "axy/codecs-base64vlq": "~1.0.0", - "axy/errors": "~1.0.1", - "ext-json": "*", - "php": ">=5.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "axy\\sourcemap\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oleg Grigoriev", - "email": "go.vasac@gmail.com" - } - ], - "description": "Work with JavaScript/CSS Source Map", - "homepage": "https://github.com/axypro/sourcemap", - "keywords": [ - "Source map", - "css", - "javascript", - "sourcemap" - ], - "support": { - "issues": "https://github.com/axypro/sourcemap/issues", - "source": "https://github.com/axypro/sourcemap/tree/0.1.5" - }, - "time": "2020-08-20T09:49:44+00:00" + "time": "2023-12-29T19:07:49+00:00" }, { "name": "bower-asset/jquery", "version": "1.12.4", "source": { "type": "git", - "url": "git@github.com:jquery/jquery-dist.git", + "url": "https://github.com/jquery/jquery-dist.git", "reference": "5e89585e0121e72ff47de177c5ef604f3089a53d" }, "dist": { @@ -1073,72 +881,6 @@ }, "time": "2021-01-25T13:15:08+00:00" }, - { - "name": "opis/closure", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.6.x-dev" - } - }, - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Opis\\Closure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" - } - ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", - "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" - }, - "time": "2022-01-27T09:35:39+00:00" - }, { "name": "predis/predis", "version": "v1.x-dev", @@ -1680,6 +1422,60 @@ }, "time": "2023-03-03T00:12:44+00:00" }, + { + "name": "shish/microbundler", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/shish/microbundler.git", + "reference": "0804687eaba424e05a0c7eaf5eb750aa24657006" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shish/microbundler/zipball/0804687eaba424e05a0c7eaf5eb750aa24657006", + "reference": "0804687eaba424e05a0c7eaf5eb750aa24657006", + "shasum": "" + }, + "require": { + "php": "^8.1", + "shish/ffsphp": "^1.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.41.1", + "phpstan/phpstan": "1.10.50", + "phpunit/phpunit": "10.5.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "MicroBundler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shish", + "email": "webmaster@shishnet.org", + "homepage": "http://shishnet.org", + "role": "Developer" + } + ], + "description": "A minimal CSS / JS bundler", + "homepage": "https://github.com/shish/microbundler", + "keywords": [ + "JS", + "bundler", + "css" + ], + "support": { + "issues": "https://github.com/shish/microbundler/issues", + "source": "https://github.com/shish/microbundler/tree/v1.0.0" + }, + "time": "2024-01-01T19:16:25+00:00" + }, { "name": "shish/microcrud", "version": "v2.1.1", @@ -1939,88 +1735,6 @@ ], "time": "2023-07-28T09:04:16+00:00" }, - { - "name": "tbela99/css", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/shish/php-css.git", - "reference": "bc343fe231a4c0fea6ef7405228f010956a5cd07" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/shish/php-css/zipball/bc343fe231a4c0fea6ef7405228f010956a5cd07", - "reference": "bc343fe231a4c0fea6ef7405228f010956a5cd07", - "shasum": "" - }, - "require": { - "axy/sourcemap": "^0.1.5", - "ext-json": "*", - "ext-mbstring": "*", - "opis/closure": "^3.6", - "php": ">=8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "suggest": { - "ext-curl": "*", - "ext-pcntl": "*", - "ext-sockets": "*" - }, - "default-branch": true, - "bin": [ - "cli/css-parser" - ], - "type": "library", - "autoload": { - "psr-4": { - "TBela\\CSS\\": "src" - } - }, - "archive": { - "exclude": [ - "*.sh", - "*.phar", - "test/", - "tool/", - "docs/", - "bin/" - ] - }, - "scripts": { - "test": [ - "./bin/runtest.sh" - ] - }, - "license": [ - "MIT", - "LGPL-3.0-or-later" - ], - "authors": [ - { - "name": "Thierry Bela", - "homepage": "https://tbela.net", - "role": "Developer" - } - ], - "description": "A CSS parser and minifier written in PHP", - "homepage": "https://github.com/tbela99/css", - "keywords": [ - "Ast", - "CSS", - "PHP", - "beautifier", - "css-parser", - "minifier", - "parser", - "stylesheet" - ], - "support": { - "source": "https://github.com/shish/php-css/tree/master" - }, - "time": "2023-08-18T18:26:28+00:00" - }, { "name": "webonyx/graphql-php", "version": "v15.8.1", @@ -2704,12 +2418,12 @@ "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "c586bf6061deb4109330c2c90f3a315b7e2dbc4a" + "reference": "4ff99e4cbba44cdbe57028ec2ccc874c8f61bbe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c586bf6061deb4109330c2c90f3a315b7e2dbc4a", - "reference": "c586bf6061deb4109330c2c90f3a315b7e2dbc4a", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/4ff99e4cbba44cdbe57028ec2ccc874c8f61bbe9", + "reference": "4ff99e4cbba44cdbe57028ec2ccc874c8f61bbe9", "shasum": "" }, "require": { @@ -2717,7 +2431,7 @@ "doctrine/instantiator": "^1.3.1 || ^2.0", "doctrine/lexer": "^2.0 || ^3.0", "jms/metadata": "^2.6", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpstan/phpdoc-parser": "^1.20" }, "require-dev": { @@ -2730,16 +2444,16 @@ "ocramius/proxy-manager": "^1.0 || ^2.0", "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.0.2", - "phpunit/phpunit": "^8.5.21 || ^9.0 || ^10.0", + "phpunit/phpunit": "^9.0 || ^10.0", "psr/container": "^1.0 || ^2.0", - "symfony/dependency-injection": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/expression-language": "^3.2 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/filesystem": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/form": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/translation": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/uid": "^5.1 || ^6.0 || ^7.0", - "symfony/validator": "^3.1.9 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/expression-language": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/form": "^5.4 || ^6.0 || ^7.0", + "symfony/translation": "^5.4 || ^6.0 || ^7.0", + "symfony/uid": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0", "twig/twig": "^1.34 || ^2.4 || ^3.0" }, "suggest": { @@ -2785,7 +2499,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/serializer/issues", - "source": "https://github.com/schmittjoh/serializer/tree/3.29.0" + "source": "https://github.com/schmittjoh/serializer/tree/master" }, "funding": [ { @@ -2793,7 +2507,7 @@ "type": "github" } ], - "time": "2023-12-09T11:57:15+00:00" + "time": "2023-12-22T14:51:09+00:00" }, { "name": "myclabs/deep-copy", @@ -2858,33 +2572,36 @@ }, { "name": "nikic/php-parser", - "version": "4.x-dev", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "1eeeb2d5252d6d8706c5a8d9d88b8d1e7ecf2109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1eeeb2d5252d6d8706c5a8d9d88b8d1e7ecf2109", + "reference": "1eeeb2d5252d6d8706c5a8d9d88b8d1e7ecf2109", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, + "default-branch": true, "bin": [ "bin/php-parse" ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2908,9 +2625,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/4.x" + "source": "https://github.com/nikic/PHP-Parser/tree/master" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2023-12-22T18:57:32+00:00" }, { "name": "phar-io/manifest", @@ -3109,16 +2826,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.4", + "version": "1.24.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496" + "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc", + "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc", "shasum": "" }, "require": { @@ -3150,9 +2867,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.5" }, - "time": "2023-11-26T18:29:22+00:00" + "time": "2023-12-16T09:33:33+00:00" }, { "name": "phpstan/phpstan", @@ -3218,23 +2935,23 @@ }, { "name": "phpunit/php-code-coverage", - "version": "dev-main", + "version": "10.1.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "599109c8ca6bae97b23482d557d2874c25a65e59" + "reference": "e8a1b365339597e7268340f4df827c2688be7f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/599109c8ca6bae97b23482d557d2874c25a65e59", - "reference": "599109c8ca6bae97b23482d557d2874c25a65e59", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e8a1b365339597e7268340f4df827c2688be7f83", + "reference": "e8a1b365339597e7268340f4df827c2688be7f83", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1", "phpunit/php-file-iterator": "^4.0", "phpunit/php-text-template": "^3.0", @@ -3252,7 +2969,6 @@ "ext-pcov": "PHP extension that provides line coverage", "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3285,7 +3001,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.10" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1" }, "funding": [ { @@ -3293,20 +3009,20 @@ "type": "github" } ], - "time": "2023-12-11T06:28:43+00:00" + "time": "2023-12-31T07:35:59+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "version": "4.1.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "reference": "b36c308bfdd69e7bbafc4348f31c83a822458ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/b36c308bfdd69e7bbafc4348f31c83a822458ec5", + "reference": "b36c308bfdd69e7bbafc4348f31c83a822458ec5", "shasum": "" }, "require": { @@ -3318,7 +3034,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "4.1-dev" } }, "autoload": { @@ -3346,7 +3062,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1" }, "funding": [ { @@ -3354,20 +3070,20 @@ "type": "github" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2023-12-31T07:36:54+00:00" }, { "name": "phpunit/php-invoker", - "version": "dev-main", + "version": "4.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "a2fd9d53892abd0b30796cf6e64e6c1ce9d3d82f" + "reference": "4dc48195da24dd8200c891fc80f4c42675837d40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/a2fd9d53892abd0b30796cf6e64e6c1ce9d3d82f", - "reference": "a2fd9d53892abd0b30796cf6e64e6c1ce9d3d82f", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/4dc48195da24dd8200c891fc80f4c42675837d40", + "reference": "4dc48195da24dd8200c891fc80f4c42675837d40", "shasum": "" }, "require": { @@ -3380,7 +3096,6 @@ "suggest": { "ext-pcntl": "*" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3411,7 +3126,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/main" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0" }, "funding": [ { @@ -3419,20 +3134,20 @@ "type": "github" } ], - "time": "2023-12-09T12:53:14+00:00" + "time": "2023-12-31T07:37:20+00:00" }, { "name": "phpunit/php-text-template", - "version": "dev-main", + "version": "3.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "36ce468c414d5fb1e0667d991b45bee6cac28c8b" + "reference": "647402cf7377b645e13d5dc7b842549296149ae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/36ce468c414d5fb1e0667d991b45bee6cac28c8b", - "reference": "36ce468c414d5fb1e0667d991b45bee6cac28c8b", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/647402cf7377b645e13d5dc7b842549296149ae9", + "reference": "647402cf7377b645e13d5dc7b842549296149ae9", "shasum": "" }, "require": { @@ -3441,7 +3156,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3472,7 +3186,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/main" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0" }, "funding": [ { @@ -3480,20 +3194,20 @@ "type": "github" } ], - "time": "2023-12-09T12:53:24+00:00" + "time": "2023-12-31T07:38:21+00:00" }, { "name": "phpunit/php-timer", - "version": "dev-main", + "version": "6.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "d118c2e2a37f6b0d5f4cd51d16c6203ada6eff48" + "reference": "ec2a787f80646fbaca26bdcf16bb52c5d358a716" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/d118c2e2a37f6b0d5f4cd51d16c6203ada6eff48", - "reference": "d118c2e2a37f6b0d5f4cd51d16c6203ada6eff48", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/ec2a787f80646fbaca26bdcf16bb52c5d358a716", + "reference": "ec2a787f80646fbaca26bdcf16bb52c5d358a716", "shasum": "" }, "require": { @@ -3502,7 +3216,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3533,7 +3246,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/main" + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0" }, "funding": [ { @@ -3541,7 +3254,7 @@ "type": "github" } ], - "time": "2023-12-09T12:53:34+00:00" + "time": "2023-12-31T07:38:42+00:00" }, { "name": "phpunit/phpunit", @@ -3902,16 +3615,16 @@ }, { "name": "sebastian/cli-parser", - "version": "dev-main", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "333fb1f9faea50aea580da4c9150e4f862d95335" + "reference": "c7c703fd197a868d43a4cd838de640ee66714c7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/333fb1f9faea50aea580da4c9150e4f862d95335", - "reference": "333fb1f9faea50aea580da4c9150e4f862d95335", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c7c703fd197a868d43a4cd838de640ee66714c7f", + "reference": "c7c703fd197a868d43a4cd838de640ee66714c7f", "shasum": "" }, "require": { @@ -3920,7 +3633,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3948,7 +3660,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/main" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0" }, "funding": [ { @@ -3956,20 +3668,20 @@ "type": "github" } ], - "time": "2023-12-09T12:50:59+00:00" + "time": "2023-12-31T07:30:32+00:00" }, { "name": "sebastian/code-unit", - "version": "dev-main", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "fc85d823fe82d442f09c28148f01f2de1406bfd5" + "reference": "1901c2cd4d50e5b1c86e067e078032708122b960" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/fc85d823fe82d442f09c28148f01f2de1406bfd5", - "reference": "fc85d823fe82d442f09c28148f01f2de1406bfd5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1901c2cd4d50e5b1c86e067e078032708122b960", + "reference": "1901c2cd4d50e5b1c86e067e078032708122b960", "shasum": "" }, "require": { @@ -3978,7 +3690,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4006,7 +3717,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/main" + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0" }, "funding": [ { @@ -4014,20 +3725,20 @@ "type": "github" } ], - "time": "2023-12-09T12:51:12+00:00" + "time": "2023-12-31T07:31:01+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "dev-main", + "version": "3.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "a5d4b197dabd362421d775b6bf18b8a6b3660739" + "reference": "35abdc17ebae0ddd0e174f9743cfe88936851758" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/a5d4b197dabd362421d775b6bf18b8a6b3660739", - "reference": "a5d4b197dabd362421d775b6bf18b8a6b3660739", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/35abdc17ebae0ddd0e174f9743cfe88936851758", + "reference": "35abdc17ebae0ddd0e174f9743cfe88936851758", "shasum": "" }, "require": { @@ -4036,7 +3747,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4063,7 +3773,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/main" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0" }, "funding": [ { @@ -4071,20 +3781,20 @@ "type": "github" } ], - "time": "2023-12-09T12:51:22+00:00" + "time": "2023-12-31T07:31:25+00:00" }, { "name": "sebastian/comparator", - "version": "dev-main", + "version": "5.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "53353f1eef05cf26c4e29cb48d5bd4c830df3725" + "reference": "06c9db507d5d6fb5efaa1a0b1a229a12e0ce8574" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/53353f1eef05cf26c4e29cb48d5bd4c830df3725", - "reference": "53353f1eef05cf26c4e29cb48d5bd4c830df3725", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/06c9db507d5d6fb5efaa1a0b1a229a12e0ce8574", + "reference": "06c9db507d5d6fb5efaa1a0b1a229a12e0ce8574", "shasum": "" }, "require": { @@ -4097,7 +3807,6 @@ "require-dev": { "phpunit/phpunit": "^10.4" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4141,7 +3850,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/main" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0" }, "funding": [ { @@ -4149,30 +3858,29 @@ "type": "github" } ], - "time": "2023-12-09T12:51:33+00:00" + "time": "2023-12-31T07:31:46+00:00" }, { "name": "sebastian/complexity", - "version": "dev-main", + "version": "3.2.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "318aa5a6acf7728c9af26d03cc6af25693b27cf5" + "reference": "7337abb9e9beaf34af915ec1497599d75b5e2f4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/318aa5a6acf7728c9af26d03cc6af25693b27cf5", - "reference": "318aa5a6acf7728c9af26d03cc6af25693b27cf5", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/7337abb9e9beaf34af915ec1497599d75b5e2f4d", + "reference": "7337abb9e9beaf34af915ec1497599d75b5e2f4d", "shasum": "" }, "require": { - "nikic/php-parser": "^4.10", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4200,7 +3908,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/main" + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2" }, "funding": [ { @@ -4208,20 +3916,20 @@ "type": "github" } ], - "time": "2023-12-09T12:51:43+00:00" + "time": "2023-12-31T07:32:09+00:00" }, { "name": "sebastian/diff", - "version": "dev-main", + "version": "5.1.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "76dcc64d78011058dc262086ae96093098dbfbc6" + "reference": "6fc9529a8e8eff86ad1f9125bad481b7c898f08f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/76dcc64d78011058dc262086ae96093098dbfbc6", - "reference": "76dcc64d78011058dc262086ae96093098dbfbc6", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6fc9529a8e8eff86ad1f9125bad481b7c898f08f", + "reference": "6fc9529a8e8eff86ad1f9125bad481b7c898f08f", "shasum": "" }, "require": { @@ -4229,9 +3937,8 @@ }, "require-dev": { "phpunit/phpunit": "^10.0", - "symfony/process": "^4.2 || ^5" + "symfony/process": "^6.4" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4268,7 +3975,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/main" + "source": "https://github.com/sebastianbergmann/diff/tree/5.1" }, "funding": [ { @@ -4276,20 +3983,20 @@ "type": "github" } ], - "time": "2023-12-09T12:51:53+00:00" + "time": "2023-12-31T07:32:59+00:00" }, { "name": "sebastian/environment", - "version": "dev-main", + "version": "6.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "b720f6f2c69d57d89b52b966026ed4271d29462e" + "reference": "a7b60a4fd8d7ee68f06d2986ae38ebfbd7224399" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/b720f6f2c69d57d89b52b966026ed4271d29462e", - "reference": "b720f6f2c69d57d89b52b966026ed4271d29462e", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a7b60a4fd8d7ee68f06d2986ae38ebfbd7224399", + "reference": "a7b60a4fd8d7ee68f06d2986ae38ebfbd7224399", "shasum": "" }, "require": { @@ -4301,7 +4008,6 @@ "suggest": { "ext-posix": "*" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4333,7 +4039,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/main" + "source": "https://github.com/sebastianbergmann/environment/tree/6.0" }, "funding": [ { @@ -4341,20 +4047,20 @@ "type": "github" } ], - "time": "2023-12-09T12:52:03+00:00" + "time": "2023-12-31T07:33:18+00:00" }, { "name": "sebastian/exporter", - "version": "dev-main", + "version": "5.1.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "032cf1796b94e920f6059f3f66ef3dec00d89166" + "reference": "0af67d5d3b795b3a034a5ecb9c494658de9ef55d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/032cf1796b94e920f6059f3f66ef3dec00d89166", - "reference": "032cf1796b94e920f6059f3f66ef3dec00d89166", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0af67d5d3b795b3a034a5ecb9c494658de9ef55d", + "reference": "0af67d5d3b795b3a034a5ecb9c494658de9ef55d", "shasum": "" }, "require": { @@ -4365,7 +4071,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4412,7 +4117,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/main" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1" }, "funding": [ { @@ -4420,20 +4125,20 @@ "type": "github" } ], - "time": "2023-12-09T12:52:13+00:00" + "time": "2023-12-31T07:33:37+00:00" }, { "name": "sebastian/global-state", - "version": "dev-main", + "version": "6.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "4a81eb9c42018f2dd746ac2181ec067f17b4cddb" + "reference": "f15849a2a7207eff11f73919dfd279e2cc85dd1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/4a81eb9c42018f2dd746ac2181ec067f17b4cddb", - "reference": "4a81eb9c42018f2dd746ac2181ec067f17b4cddb", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/f15849a2a7207eff11f73919dfd279e2cc85dd1b", + "reference": "f15849a2a7207eff11f73919dfd279e2cc85dd1b", "shasum": "" }, "require": { @@ -4445,7 +4150,6 @@ "ext-dom": "*", "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4475,7 +4179,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/main" + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0" }, "funding": [ { @@ -4483,30 +4187,29 @@ "type": "github" } ], - "time": "2023-12-09T12:52:23+00:00" + "time": "2023-12-31T07:33:55+00:00" }, { "name": "sebastian/lines-of-code", - "version": "dev-main", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c341778c2bda41ce598dc02f0912876da5d07cd3" + "reference": "29f7720f283062763daf59d02499b569fd0b53e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c341778c2bda41ce598dc02f0912876da5d07cd3", - "reference": "c341778c2bda41ce598dc02f0912876da5d07cd3", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/29f7720f283062763daf59d02499b569fd0b53e4", + "reference": "29f7720f283062763daf59d02499b569fd0b53e4", "shasum": "" }, "require": { - "nikic/php-parser": "^4.10", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4534,7 +4237,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/main" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0" }, "funding": [ { @@ -4542,20 +4245,20 @@ "type": "github" } ], - "time": "2023-12-09T12:52:34+00:00" + "time": "2023-12-31T07:34:13+00:00" }, { "name": "sebastian/object-enumerator", - "version": "dev-main", + "version": "5.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "0c9ec982ff95cc83cce2a7811be6c07398bcf353" + "reference": "8df0d10e04532a48d17078a4cb504bfcfc3a1101" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/0c9ec982ff95cc83cce2a7811be6c07398bcf353", - "reference": "0c9ec982ff95cc83cce2a7811be6c07398bcf353", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/8df0d10e04532a48d17078a4cb504bfcfc3a1101", + "reference": "8df0d10e04532a48d17078a4cb504bfcfc3a1101", "shasum": "" }, "require": { @@ -4566,7 +4269,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4593,7 +4295,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/main" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0" }, "funding": [ { @@ -4601,20 +4303,20 @@ "type": "github" } ], - "time": "2023-12-09T12:52:44+00:00" + "time": "2023-12-31T07:34:38+00:00" }, { "name": "sebastian/object-reflector", - "version": "dev-main", + "version": "3.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "fd1560a9909ac46ac6c9bf4ef34a8c2d61cd989d" + "reference": "f6eb82d03f39cb1d13ae9ad9b7cb745144ecacda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/fd1560a9909ac46ac6c9bf4ef34a8c2d61cd989d", - "reference": "fd1560a9909ac46ac6c9bf4ef34a8c2d61cd989d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/f6eb82d03f39cb1d13ae9ad9b7cb745144ecacda", + "reference": "f6eb82d03f39cb1d13ae9ad9b7cb745144ecacda", "shasum": "" }, "require": { @@ -4623,7 +4325,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4650,7 +4351,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/main" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0" }, "funding": [ { @@ -4658,20 +4359,20 @@ "type": "github" } ], - "time": "2023-12-09T12:52:54+00:00" + "time": "2023-12-31T07:35:14+00:00" }, { "name": "sebastian/recursion-context", - "version": "dev-main", + "version": "5.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "c667db4d144287807077438891c97eb4af356e2e" + "reference": "f7894029dc8a280837f77d643e881b53b6a04677" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c667db4d144287807077438891c97eb4af356e2e", - "reference": "c667db4d144287807077438891c97eb4af356e2e", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f7894029dc8a280837f77d643e881b53b6a04677", + "reference": "f7894029dc8a280837f77d643e881b53b6a04677", "shasum": "" }, "require": { @@ -4680,7 +4381,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4715,7 +4415,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/main" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0" }, "funding": [ { @@ -4723,20 +4423,20 @@ "type": "github" } ], - "time": "2023-12-09T12:53:43+00:00" + "time": "2023-12-31T07:40:00+00:00" }, { "name": "sebastian/type", - "version": "dev-main", + "version": "4.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81e33408e8812ba0abe464fbc071149ca24fe7e7" + "reference": "4337b83f7f8e9260afb858162b7c801a728c0353" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81e33408e8812ba0abe464fbc071149ca24fe7e7", - "reference": "81e33408e8812ba0abe464fbc071149ca24fe7e7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/4337b83f7f8e9260afb858162b7c801a728c0353", + "reference": "4337b83f7f8e9260afb858162b7c801a728c0353", "shasum": "" }, "require": { @@ -4745,7 +4445,6 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4773,7 +4472,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/main" + "source": "https://github.com/sebastianbergmann/type/tree/4.0" }, "funding": [ { @@ -4781,26 +4480,25 @@ "type": "github" } ], - "time": "2023-12-09T12:53:55+00:00" + "time": "2023-12-31T07:40:32+00:00" }, { "name": "sebastian/version", - "version": "dev-main", + "version": "4.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "11642ad6e1c849da06f3f1f77e57e236e1cf6c4f" + "reference": "116978f15ca072d5df4dab9419a32a66e386830d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/11642ad6e1c849da06f3f1f77e57e236e1cf6c4f", - "reference": "11642ad6e1c849da06f3f1f77e57e236e1cf6c4f", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/116978f15ca072d5df4dab9419a32a66e386830d", + "reference": "116978f15ca072d5df4dab9419a32a66e386830d", "shasum": "" }, "require": { "php": ">=8.1" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4828,7 +4526,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/main" + "source": "https://github.com/sebastianbergmann/version/tree/4.0" }, "funding": [ { @@ -4836,7 +4534,7 @@ "type": "github" } ], - "time": "2023-12-09T12:54:05+00:00" + "time": "2023-12-31T07:41:08+00:00" }, { "name": "symfony/console", @@ -4938,12 +4636,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "d76d2632cfc2206eecb5ad2b26cd5934082941b6" + "reference": "e95216850555cd55e71b857eb9d6c2674124603a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d76d2632cfc2206eecb5ad2b26cd5934082941b6", - "reference": "d76d2632cfc2206eecb5ad2b26cd5934082941b6", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e95216850555cd55e71b857eb9d6c2674124603a", + "reference": "e95216850555cd55e71b857eb9d6c2674124603a", "shasum": "" }, "require": { @@ -5010,7 +4708,7 @@ "type": "tidelift" } ], - "time": "2023-07-27T06:52:43+00:00" + "time": "2023-12-27T22:16:42+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5703,12 +5401,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c3649cf286a89a8c67cb9789011e9b36733f0e3e" + "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c3649cf286a89a8c67cb9789011e9b36733f0e3e", - "reference": "c3649cf286a89a8c67cb9789011e9b36733f0e3e", + "url": "https://api.github.com/repos/symfony/process/zipball/c4b1ef0bc80533d87a2e969806172f1c2a980241", + "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241", "shasum": "" }, "require": { @@ -5756,7 +5454,7 @@ "type": "tidelift" } ], - "time": "2023-12-02T12:49:56+00:00" + "time": "2023-12-22T16:42:54+00:00" }, { "name": "symfony/service-contracts", @@ -5764,17 +5462,17 @@ "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838" + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838", - "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^2.0" + "psr/container": "^1.1|^2.0" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -5823,7 +5521,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.1" }, "funding": [ { @@ -5839,7 +5537,7 @@ "type": "tidelift" } ], - "time": "2023-07-30T20:28:31+00:00" + "time": "2023-12-26T14:02:43+00:00" }, { "name": "symfony/stopwatch", @@ -6044,8 +5742,7 @@ "minimum-stability": "dev", "stability-flags": { "shish/gqla": 20, - "naroga/redis-cache": 20, - "tbela99/css": 20 + "naroga/redis-cache": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/core/basepage.php b/core/basepage.php index 18617e69f8..bd41e826b9 100644 --- a/core/basepage.php +++ b/core/basepage.php @@ -5,8 +5,6 @@ namespace Shimmie2; use MicroHTML\HTMLElement; -use TBela\CSS\Parser; -use TBela\CSS\Renderer; require_once "core/event.php"; @@ -399,26 +397,11 @@ private function get_css_cache_file(string $theme_name, int $config_latest): str $css_md5 = md5(serialize($css_files)); $css_cache_file = data_path("cache/style/{$theme_name}.{$css_latest}.{$css_md5}.css"); if (!file_exists($css_cache_file)) { - // the CSS minifier causes a bunch of deprecation warnings, - // so we turn off error reporting while it runs - $old_error_level = error_reporting(error_reporting(null) & ~E_DEPRECATED); - $parser = new Parser(); - foreach($css_files as $file) { - $parser->append($file); + $mcss = new \MicroBundler\MicroBundler(); + foreach($css_files as $css) { + $mcss->addSource($css, file_get_contents($css)); } - $element = $parser->parse(); - - // minified output - $renderer = new Renderer([ - 'compress' => true, - 'convert_color' => 'hex', - 'css_level' => 3, - 'sourcemap' => true, - 'allow_duplicate_declarations' => false, - 'legacy_rendering' => true, // turn nested CSS into regular - ]); - $renderer->save($element, $css_cache_file); - error_reporting($old_error_level); + $mcss->save($css_cache_file); } return $css_cache_file; @@ -442,11 +425,11 @@ private function get_js_cache_file(string $theme_name, int $config_latest): stri $js_md5 = md5(serialize($js_files)); $js_cache_file = data_path("cache/script/{$theme_name}.{$js_latest}.{$js_md5}.js"); if (!file_exists($js_cache_file)) { - $js_data = ""; - foreach ($js_files as $file) { - $js_data .= file_get_contents($file) . "\n"; + $mcss = new \MicroBundler\MicroBundler(); + foreach($js_files as $js) { + $mcss->addSource($js, file_get_contents($js)); } - file_put_contents($js_cache_file, $js_data); + $mcss->save($js_cache_file); } return $js_cache_file; From c80197ecaf9f5b69da3c7ff9b15c9d395f2088ae Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 20:01:56 +0000 Subject: [PATCH 142/154] bump --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index dcd6e1b67e..3183e5b350 100644 --- a/composer.lock +++ b/composer.lock @@ -1424,16 +1424,16 @@ }, { "name": "shish/microbundler", - "version": "v1.0.0", + "version": "v1.0.1", "source": { "type": "git", "url": "https://github.com/shish/microbundler.git", - "reference": "0804687eaba424e05a0c7eaf5eb750aa24657006" + "reference": "5f48327e92d3601f2c86caef2856b9671a63b6b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shish/microbundler/zipball/0804687eaba424e05a0c7eaf5eb750aa24657006", - "reference": "0804687eaba424e05a0c7eaf5eb750aa24657006", + "url": "https://api.github.com/repos/shish/microbundler/zipball/5f48327e92d3601f2c86caef2856b9671a63b6b0", + "reference": "5f48327e92d3601f2c86caef2856b9671a63b6b0", "shasum": "" }, "require": { @@ -1472,9 +1472,9 @@ ], "support": { "issues": "https://github.com/shish/microbundler/issues", - "source": "https://github.com/shish/microbundler/tree/v1.0.0" + "source": "https://github.com/shish/microbundler/tree/v1.0.1" }, - "time": "2024-01-01T19:16:25+00:00" + "time": "2024-01-01T19:58:28+00:00" }, { "name": "shish/microcrud", From 509f8eee3e674882dd4bc6975ee4c3a1940ea0a0 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 20:03:50 +0000 Subject: [PATCH 143/154] [core] separate init.js for code which needs to run before page load --- core/basepage.php | 27 ++++++++++++++++++++++++++- ext/static_files/init.js | 6 ++++++ ext/static_files/script.js | 9 --------- 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 ext/static_files/init.js diff --git a/core/basepage.php b/core/basepage.php index bd41e826b9..f151e2f954 100644 --- a/core/basepage.php +++ b/core/basepage.php @@ -380,6 +380,9 @@ public function add_auto_html_headers(): void $css_cache_file = $this->get_css_cache_file($theme_name, $config_latest); $this->add_html_header("", 43); + $initjs_cache_file = $this->get_initjs_cache_file($theme_name, $config_latest); + $this->add_html_header("", 44); + $js_cache_file = $this->get_js_cache_file($theme_name, $config_latest); $this->add_html_header("", 44); } @@ -407,6 +410,29 @@ private function get_css_cache_file(string $theme_name, int $config_latest): str return $css_cache_file; } + private function get_initjs_cache_file(string $theme_name, int $config_latest): string + { + $js_latest = $config_latest; + $js_files = array_merge( + zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/init.js"), + zglob("themes/$theme_name/init.js") + ); + foreach ($js_files as $js) { + $js_latest = max($js_latest, filemtime($js)); + } + $js_md5 = md5(serialize($js_files)); + $js_cache_file = data_path("cache/initscript/{$theme_name}.{$js_latest}.{$js_md5}.js"); + if (!file_exists($js_cache_file)) { + $mcss = new \MicroBundler\MicroBundler(); + foreach($js_files as $js) { + $mcss->addSource($js, file_get_contents($js)); + } + $mcss->save($js_cache_file); + } + + return $js_cache_file; + } + private function get_js_cache_file(string $theme_name, int $config_latest): string { $js_latest = $config_latest; @@ -435,7 +461,6 @@ private function get_js_cache_file(string $theme_name, int $config_latest): stri return $js_cache_file; } - /** * @return array A list of stylesheets relative to the theme root. */ diff --git a/ext/static_files/init.js b/ext/static_files/init.js new file mode 100644 index 0000000000..d45f34e39c --- /dev/null +++ b/ext/static_files/init.js @@ -0,0 +1,6 @@ +function shm_cookie_set(name, value) { + Cookies.set(name, value, {expires: 365, samesite: "lax", path: "/"}); +} +function shm_cookie_get(name) { + return Cookies.get(name); +} diff --git a/ext/static_files/script.js b/ext/static_files/script.js index 61eb16b339..889e148383 100644 --- a/ext/static_files/script.js +++ b/ext/static_files/script.js @@ -1,12 +1,3 @@ -/*jshint bitwise:false, curly:true, eqeqeq:true, evil:true, forin:false, noarg:true, noempty:true, nonew:true, undef:false, strict:false, browser:true */ - -function shm_cookie_set(name, value) { - Cookies.set(name, value, {expires: 365, samesite: "lax", path: "/"}); -} -function shm_cookie_get(name) { - return Cookies.get(name); -} - document.addEventListener('DOMContentLoaded', () => { /** Load jQuery extensions **/ //Code via: https://stackoverflow.com/a/13106698 From 8755298ee9443b9ea1d500d64c595452220b1ca0 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 20:15:34 +0000 Subject: [PATCH 144/154] [static] add shm_log js function available early in page load --- ext/static_files/init.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/static_files/init.js b/ext/static_files/init.js index d45f34e39c..5cd4e5f854 100644 --- a/ext/static_files/init.js +++ b/ext/static_files/init.js @@ -4,3 +4,13 @@ function shm_cookie_set(name, value) { function shm_cookie_get(name) { return Cookies.get(name); } + +function shm_log(...message) { + window.dispatchEvent(new CustomEvent("shm_log", {detail: {message: message}})); +} +window.addEventListener("shm_log", function (e) { + console.log(...e.detail.message); +}); +window.addEventListener("error", function (e) { + shm_log("Window error:", e.error); +}); From aea533b345a642a0d4b1030c5a9271cc91a614e8 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 20:21:51 +0000 Subject: [PATCH 145/154] [file handlers] separate out display_image and display_metadata --- core/extension.php | 7 +++++-- ext/handle_cbz/theme.php | 3 ++- ext/handle_ico/theme.php | 3 ++- ext/handle_mp3/theme.php | 3 ++- ext/handle_pixel/theme.php | 29 +++++++++++++++++++++-------- ext/handle_svg/theme.php | 3 ++- ext/handle_video/theme.php | 4 ++-- 7 files changed, 36 insertions(+), 16 deletions(-) diff --git a/core/extension.php b/core/extension.php index 296934139d..65d906942e 100644 --- a/core/extension.php +++ b/core/extension.php @@ -374,10 +374,13 @@ public function onThumbnailGeneration(ThumbnailGenerationEvent $event) public function onDisplayingImage(DisplayingImageEvent $event) { - global $page; + global $config, $page; if ($this->supported_mime($event->image->get_mime())) { // @phpstan-ignore-next-line - $this->theme->display_image($page, $event->image); + $this->theme->display_image($event->image); + if ($config->get_bool(ImageConfig::SHOW_META) && method_exists($this->theme, "display_metadata")) { + $this->theme->display_metadata($event->image); + } } } diff --git a/ext/handle_cbz/theme.php b/ext/handle_cbz/theme.php index ce574a8f54..5d6dff37d3 100644 --- a/ext/handle_cbz/theme.php +++ b/ext/handle_cbz/theme.php @@ -6,8 +6,9 @@ class CBZFileHandlerTheme extends Themelet { - public function display_image(Page $page, Image $image) + public function display_image(Image $image) { + global $page; $data_href = get_base_href(); $ilink = $image->get_image_link(); $html = " diff --git a/ext/handle_ico/theme.php b/ext/handle_ico/theme.php index 81f6c74fae..2f2587c090 100644 --- a/ext/handle_ico/theme.php +++ b/ext/handle_ico/theme.php @@ -6,8 +6,9 @@ class IcoFileHandlerTheme extends Themelet { - public function display_image(Page $page, Image $image) + public function display_image(Image $image) { + global $page; $ilink = $image->get_image_link(); $html = " main imageget_image_link(); $html = " diff --git a/ext/handle_pixel/theme.php b/ext/handle_pixel/theme.php index 3bf5b3022b..62c465852e 100644 --- a/ext/handle_pixel/theme.php +++ b/ext/handle_pixel/theme.php @@ -4,14 +4,31 @@ namespace Shimmie2; +use function MicroHTML\IMG; + class PixelFileHandlerTheme extends Themelet { - public function display_image(Page $page, Image $image) + public function display_image(Image $image) + { + global $config, $page; + + $html = IMG([ + 'alt' => 'main image', + 'class' => 'shm-main-image', + 'id' => 'main_image', + 'src' => $image->get_image_link(), + 'data-width' => $image->width, + 'data-height' => $image->height, + 'data-mime' => $image->get_mime(), + ]); + $page->add_block(new Block("Image", $html, "main", 10)); + } + + public function display_metadata(Image $image) { - global $config; + global $page; - $u_ilink = $image->get_image_link(); - if ($config->get_bool(ImageConfig::SHOW_META) && function_exists(ImageIO::EXIF_READ_FUNCTION)) { + if (function_exists(ImageIO::EXIF_READ_FUNCTION)) { # FIXME: only read from jpegs? $exif = @exif_read_data($image->get_image_filename(), "IFD0", true); if ($exif) { @@ -32,9 +49,5 @@ public function display_image(Page $page, Image $image) } } } - - $html = "main image"; - $page->add_block(new Block("Image", $html, "main", 10)); } } diff --git a/ext/handle_svg/theme.php b/ext/handle_svg/theme.php index 3755758161..b7f7b51362 100644 --- a/ext/handle_svg/theme.php +++ b/ext/handle_svg/theme.php @@ -6,8 +6,9 @@ class SVGFileHandlerTheme extends Themelet { - public function display_image(Page $page, Image $image) + public function display_image(Image $image) { + global $page; $ilink = make_link("get_svg/{$image->id}/{$image->id}.svg"); // $ilink = $image->get_image_link(); $html = " diff --git a/ext/handle_video/theme.php b/ext/handle_video/theme.php index 076b4d566d..dcb702089e 100644 --- a/ext/handle_video/theme.php +++ b/ext/handle_video/theme.php @@ -8,9 +8,9 @@ class VideoFileHandlerTheme extends Themelet { - public function display_image(Page $page, Image $image) + public function display_image(Image $image) { - global $config; + global $config, $page; $width = "auto"; if ($image->width > 1) { From ec567d399aabbf28fd33296db15f77e7f7d7ffc1 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 21:16:47 +0000 Subject: [PATCH 146/154] log image loading errors --- ext/handle_pixel/theme.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/handle_pixel/theme.php b/ext/handle_pixel/theme.php index 62c465852e..0ffebd7755 100644 --- a/ext/handle_pixel/theme.php +++ b/ext/handle_pixel/theme.php @@ -20,6 +20,7 @@ public function display_image(Image $image) 'data-width' => $image->width, 'data-height' => $image->height, 'data-mime' => $image->get_mime(), + 'onerror' => "shm_log('Error loading >>{$image->id}')", ]); $page->add_block(new Block("Image", $html, "main", 10)); } From e4ce96fe6ca0d791bd921d2dee568220ac1908cd Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 21:16:54 +0000 Subject: [PATCH 147/154] font tweak --- themes/rule34v2/upload.theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/rule34v2/upload.theme.php b/themes/rule34v2/upload.theme.php index a40f7f4c87..20f3e05a6b 100644 --- a/themes/rule34v2/upload.theme.php +++ b/themes/rule34v2/upload.theme.php @@ -39,6 +39,6 @@ public function display_full(Page $page): void // the big page protected function build_upload_block(): HTMLElement { - return A(["href" => make_link("upload"), "style" => 'font-size: 2rem; display: block;'], "Upload"); + return A(["href" => make_link("upload"), "style" => 'font-size: 1.7rem; display: block;'], "Upload"); } } From a267e502f3d76bdabadfe3f0a0d8a61cef273255 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 21:17:36 +0000 Subject: [PATCH 148/154] fmt --- ext/handle_mp3/theme.php | 2 +- ext/handle_svg/theme.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/handle_mp3/theme.php b/ext/handle_mp3/theme.php index ef221c6dd4..a42a3a16ed 100644 --- a/ext/handle_mp3/theme.php +++ b/ext/handle_mp3/theme.php @@ -8,7 +8,7 @@ class MP3FileHandlerTheme extends Themelet { public function display_image(Image $image) { - global $page; + global $page; $data_href = get_base_href(); $ilink = $image->get_image_link(); $html = " diff --git a/ext/handle_svg/theme.php b/ext/handle_svg/theme.php index b7f7b51362..a9fd63df56 100644 --- a/ext/handle_svg/theme.php +++ b/ext/handle_svg/theme.php @@ -8,7 +8,7 @@ class SVGFileHandlerTheme extends Themelet { public function display_image(Image $image) { - global $page; + global $page; $ilink = make_link("get_svg/{$image->id}/{$image->id}.svg"); // $ilink = $image->get_image_link(); $html = " From fab4b489ac74a98c90b78a627fb507b5d8948d60 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 22:27:42 +0000 Subject: [PATCH 149/154] infobox css to css file, and a little wider --- ext/view/style.css | 5 +++++ ext/view/theme.php | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/view/style.css b/ext/view/style.css index b49fa7b987..327cd01b8b 100644 --- a/ext/view/style.css +++ b/ext/view/style.css @@ -1,3 +1,8 @@ +.image_info { + width: 550px; + max-width: 100%; +} + .image_info .edit { display: block; } diff --git a/ext/view/theme.php b/ext/view/theme.php index e14d52ba54..657a5404f3 100644 --- a/ext/view/theme.php +++ b/ext/view/theme.php @@ -105,7 +105,6 @@ protected function build_info(Image $image, $editor_parts): HTMLElement TABLE( [ "class" => "image_info form", - "style" => "width: 500px; max-width: 100%;" ], ...$editor_parts, ), From 164e6df9f8ace43a7629b98f09ebb0308fbace1c Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 22:38:01 +0000 Subject: [PATCH 150/154] derp --- ext/view/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/view/style.css b/ext/view/style.css index 327cd01b8b..b321d98e0a 100644 --- a/ext/view/style.css +++ b/ext/view/style.css @@ -1,4 +1,4 @@ -.image_info { +TABLE.form.image_info { width: 550px; max-width: 100%; } From 20b72f5cfbbe64a349b1c6c6786c30e3b48c0c43 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 23:32:42 +0000 Subject: [PATCH 151/154] [tag edit] textarea for more tagging --- ext/tag_edit/theme.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ext/tag_edit/theme.php b/ext/tag_edit/theme.php index 37dfcc696d..57356955cb 100644 --- a/ext/tag_edit/theme.php +++ b/ext/tag_edit/theme.php @@ -6,7 +6,7 @@ use MicroHTML\HTMLElement; -use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A}; +use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A, TEXTAREA}; class TagEditTheme extends Themelet { @@ -57,14 +57,12 @@ public function get_tag_editor_html(Image $image): HTMLElement return SHM_POST_INFO( "Tags", joinHTML(", ", $tag_links), - $user->can(Permissions::EDIT_IMAGE_TAG) ? INPUT([ + $user->can(Permissions::EDIT_IMAGE_TAG) ? TEXTAREA([ "class" => "autocomplete_tags", "type" => "text", "name" => "tag_edit__tags", - "value" => $image->get_tag_list(), "id" => "tag_editor", - "autocomplete" => "off" - ]) : null, + ], $image->get_tag_list()) : null, link: Extension::is_enabled(TagHistoryInfo::KEY) ? make_link("tag_history/{$image->id}") : null, ); } From 6d238578f82d60a1575b5b1f40c2e10d191de447 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 23:54:23 +0000 Subject: [PATCH 152/154] github?? --- .github/workflows/publish.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 933067f7ec..82d18f9096 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,13 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' }} steps: - - name: Checkout + - name: Checkout triggering commit + if: ${{ github.event_name == 'workflow_run' }} + uses: actions/checkout@master + with: + ref: if: ${{ github.event.workflow_run.head_sha }} + - name: Checkout main commit + if: ${{ github.event_name != 'workflow_run' }} uses: actions/checkout@master - name: Set build vars run: | From 6840f3e144f5277b5b9fbef39500a7e57f1da6b1 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 23:54:54 +0000 Subject: [PATCH 153/154] github?? --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 82d18f9096..07e14207e1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,7 +22,7 @@ jobs: if: ${{ github.event_name == 'workflow_run' }} uses: actions/checkout@master with: - ref: if: ${{ github.event.workflow_run.head_sha }} + ref: ${{ github.event.workflow_run.head_sha }} - name: Checkout main commit if: ${{ github.event_name != 'workflow_run' }} uses: actions/checkout@master From 47c92894f1fbe3cbad66c8f6dbbdd83a2bde4b3d Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jan 2024 23:58:47 +0000 Subject: [PATCH 154/154] test --- ext/tag_edit/theme.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/tag_edit/theme.php b/ext/tag_edit/theme.php index 57356955cb..408b976ccd 100644 --- a/ext/tag_edit/theme.php +++ b/ext/tag_edit/theme.php @@ -63,7 +63,9 @@ public function get_tag_editor_html(Image $image): HTMLElement "name" => "tag_edit__tags", "id" => "tag_editor", ], $image->get_tag_list()) : null, - link: Extension::is_enabled(TagHistoryInfo::KEY) ? make_link("tag_history/{$image->id}") : null, + link: Extension::is_enabled(TagHistoryInfo::KEY) ? + make_link("tag_history/{$image->id}") : + null, ); }
        {$entry["date_sent"]}{$entry["message"]}