diff --git a/.travis.yml b/.travis.yml index d2a0a3c2a1..7ea68bd9d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,8 @@ env: php: - 5.5 - 5.6 - - 7.0 matrix: fast_finish: true - allow_failures: - - php: 7.0 services: - mysql - memcached diff --git a/bower.json b/bower.json index b218c5231c..60d774aeb5 100644 --- a/bower.json +++ b/bower.json @@ -27,7 +27,7 @@ "bootstrap-sass": "v2.3.2.2", "jquery.lazyload": "~1.9.7", "jquery-treeview": "~1.4.2", - "alchemy-embed-medias": "~0.1.0" + "alchemy-embed-medias": "~0.2.0" }, "devDependencies": { "mocha": "latest", diff --git a/circle.yml b/circle.yml index 23fa3b36f8..374fa1bb25 100644 --- a/circle.yml +++ b/circle.yml @@ -36,14 +36,14 @@ database: override: - mysql -u ubuntu -e 'CREATE DATABASE update39_test;CREATE DATABASE ab_test;CREATE DATABASE db_test;SET @@global.sql_mode=STRICT_ALL_TABLES;SET @@global.max_allowed_packet=33554432;SET @@global.wait_timeout=999999;'; post: - - "./bin/developer system:uninstall" - - "./bin/setup system:install --email=test@phraseanet.com --password=test --db-host=127.0.0.1 --db-user=ubuntu --db-template=fr --db-password= --databox=db_test --appbox=ab_test --server-name=http://127.0.0.1 -y;" - - "./bin/developer ini:setup-tests-dbs" - - "./bin/console searchengine:index:create" - - "./bin/developer phraseanet:regenerate-sqlite" - - "./bin/developer phraseanet:generate-js-fixtures" + - "./bin/developer system:uninstall -v" + - "./bin/setup system:install -v --email=test@phraseanet.com --password=test --db-host=127.0.0.1 --db-user=ubuntu --db-template=fr --db-password= --databox=db_test --appbox=ab_test --server-name=http://127.0.0.1 -y;" + - "./bin/developer ini:setup-tests-dbs -v" + - "./bin/console searchengine:index:create -v" + - "./bin/developer phraseanet:regenerate-sqlite -v" + - "./bin/developer phraseanet:generate-js-fixtures -v" test: override: - - case $CIRCLE_NODE_INDEX in 0) php -d memory_limit=-1 bin/phpunit --exclude-group legacy ;; 1) ./node_modules/.bin/gulp test ;; 2) php -d memory_limit=-1 bin/phpunit --group legacy --exclude-group web ;; 3) php -d memory_limit=-1 bin/phpunit --group web ;; esac: + - case $CIRCLE_NODE_INDEX in 0) php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit.xml --exclude-group legacy ;; 1) ./node_modules/.bin/gulp test ;; 2) php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit.xml --group legacy --exclude-group web ;; 3) php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit.xml --group web ;; esac: parallel: true diff --git a/composer.json b/composer.json index ec712af4dc..0ea69c8241 100644 --- a/composer.json +++ b/composer.json @@ -22,10 +22,6 @@ "type": "vcs", "url": "https://github.com/alchemy-fr/JMSTranslationBundle" }, - { - "type": "git", - "url": "https://github.com/alchemy-fr/symfony-cors" - }, { "type": "vcs", "url": "https://github.com/alchemy-fr/embed-bundle.git" @@ -34,7 +30,7 @@ "require": { "php": ">=5.5", "alchemy-fr/tcpdf-clone": "~6.0", - "alchemy/embed-bundle": "~0.1.0", + "alchemy/embed-bundle": "^0.2.0", "alchemy/geonames-api-consumer": "~0.1.0", "alchemy/google-plus-api-client": "~0.6.2", "alchemy/mediavorus": "^0.4.4", @@ -68,6 +64,8 @@ "jms/serializer": "~0.10", "jms/translation-bundle": "dev-rebase-2015-10-20", "justinrainbow/json-schema": "~1.3", + "league/flysystem": "^1.0", + "league/flysystem-aws-s3-v2": "^1.0", "media-alchemyst/media-alchemyst": "~0.4", "monolog/monolog": "~1.3", "mrclay/minify": "~2.1.6", @@ -113,7 +111,7 @@ "include-path": ["vendor/zend/gdata/library"], "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "4.1.x-dev" } } } diff --git a/composer.lock b/composer.lock index 8d727d910f..200fc46c55 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "32865d9f1fb5a0d8ead56364bc5db5c7", - "content-hash": "1212545fbe0ee56e58d31f099da0e819", + "hash": "8a411f7150eeb12552a747c083ff0e6a", + "content-hash": "898d26642c158c5efb98e8abaccc2655", "packages": [ { "name": "alchemy-fr/tcpdf-clone", @@ -122,16 +122,16 @@ }, { "name": "alchemy/embed-bundle", - "version": "v0.1.0", + "version": "v0.2.0", "source": { "type": "git", "url": "https://github.com/alchemy-fr/embed-bundle.git", - "reference": "fc5fb948b15b10262a8da52e54e11fe1d2ed2904" + "reference": "37cc6ab627e4a770a60aad4ae4c60934e5de5cf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/embed-bundle/zipball/fc5fb948b15b10262a8da52e54e11fe1d2ed2904", - "reference": "fc5fb948b15b10262a8da52e54e11fe1d2ed2904", + "url": "https://api.github.com/repos/alchemy-fr/embed-bundle/zipball/37cc6ab627e4a770a60aad4ae4c60934e5de5cf5", + "reference": "37cc6ab627e4a770a60aad4ae4c60934e5de5cf5", "shasum": "" }, "require-dev": { @@ -169,10 +169,10 @@ ], "description": "Embed resources bundle", "support": { - "source": "https://github.com/alchemy-fr/embed-bundle/tree/v0.1.0", + "source": "https://github.com/alchemy-fr/embed-bundle/tree/v0.2.0", "issues": "https://github.com/alchemy-fr/embed-bundle/issues" }, - "time": "2015-12-21 10:19:25" + "time": "2016-01-15 12:49:06" }, { "name": "alchemy/geonames-api-consumer", @@ -487,7 +487,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/rest-bundle/zipball/a0e2c2b8a1d2c9c405fc9663b698f56a20831946", + "url": "https://api.github.com/repos/alchemy-fr/rest-bundle/zipball/68d2333231867c4157db27f6f4972328b1433eb1", "reference": "7b1c88c02ab8c0d4e997fd61c13c8fd4c3ce5216", "shasum": "" }, @@ -533,8 +533,8 @@ "version": "0.1.1", "source": { "type": "git", - "url": "https://github.com/alchemy-fr/symfony-cors", - "reference": "9e7f249e689e66f05a1e3d4d166224aaa1e9cc63" + "url": "https://github.com/alchemy-fr/symfony-cors.git", + "reference": "dbf7fcff1ce9fc1265db12955476ff169eab7375" }, "require": { "symfony/http-kernel": "^2.3.0|^3.0.0" @@ -697,6 +697,69 @@ ], "time": "2015-12-15 11:29:00" }, + { + "name": "aws/aws-sdk-php", + "version": "2.8.24", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "54b67f902bb2c5bbab481bc3c46537752a018830" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/54b67f902bb2c5bbab481bc3c46537752a018830", + "reference": "54b67f902bb2c5bbab481bc3c46537752a018830", + "shasum": "" + }, + "require": { + "guzzle/guzzle": "~3.7", + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/cache": "~1.0", + "ext-openssl": "*", + "monolog/monolog": "~1.4", + "phpunit/phpunit": "~4.0", + "phpunit/phpunit-mock-objects": "2.3.1", + "symfony/yaml": "~2.1" + }, + "suggest": { + "doctrine/cache": "Adds support for caching of credentials and responses", + "ext-apc": "Allows service description opcode caching, request and response caching, and credentials caching", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "monolog/monolog": "Adds support for logging HTTP requests and responses", + "symfony/yaml": "Eases the ability to write manifests for creating jobs in AWS Import/Export" + }, + "type": "library", + "autoload": { + "psr-0": { + "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" + ], + "time": "2015-11-16 22:34:08" + }, { "name": "beberlei/assert", "version": "v2.4", @@ -2583,7 +2646,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hoaproject/Stream/zipball/011ab91d942f1d7096deade4c8a10fe57d51c5b3", + "url": "https://api.github.com/repos/hoaproject/Stream/zipball/3bc446bc00849bf51166adc415d77aa375d48d8c", "reference": "011ab91d942f1d7096deade4c8a10fe57d51c5b3", "shasum": "" }, @@ -2849,6 +2912,48 @@ }, "time": "2015-01-13 18:12:26" }, + { + "name": "ircmaxell/password-compat", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ], + "time": "2014-11-20 16:49:30" + }, { "name": "ircmaxell/random-lib", "version": "v1.1.0", @@ -3179,16 +3284,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "a4bee9f4b344b66e0a0d96c7afae1e92edf385fe" + "reference": "f9e27c3e202faf14fd581ef41355d83bb4b7eb7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/a4bee9f4b344b66e0a0d96c7afae1e92edf385fe", - "reference": "a4bee9f4b344b66e0a0d96c7afae1e92edf385fe", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/f9e27c3e202faf14fd581ef41355d83bb4b7eb7d", + "reference": "f9e27c3e202faf14fd581ef41355d83bb4b7eb7d", "shasum": "" }, "require": { @@ -3241,7 +3346,138 @@ "json", "schema" ], - "time": "2015-09-08 22:28:04" + "time": "2016-01-06 14:37:04" + }, + { + "name": "league/flysystem", + "version": "1.0.16", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/183e1a610664baf6dcd6fceda415baf43cbdc031", + "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "ext-fileinfo": "*", + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^2.2", + "phpspec/prophecy-phpunit": "~1.0", + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-copy": "Allows you to use Copy.com storage", + "league/flysystem-dropbox": "Allows you to use Dropbox storage", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2015-12-19 20:16:43" + }, + { + "name": "league/flysystem-aws-s3-v2", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v2.git", + "reference": "e464a35e34abed572c76b76aa2f278d178e141c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v2/zipball/e464a35e34abed572c76b76aa2f278d178e141c6", + "reference": "e464a35e34abed572c76b76aa2f278d178e141c6", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "~2.7", + "league/flysystem": "~1.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v2\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for AWS S3 SDK v2", + "time": "2015-10-15 15:55:48" }, { "name": "league/fractal", @@ -3921,6 +4157,54 @@ ], "time": "2014-10-06 10:57:25" }, + { + "name": "paragonie/random_compat", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/dd8998b7c846f6909f4e7a5f67fabebfc412a4f7", + "reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2016-01-06 13:31:20" + }, { "name": "php-ffmpeg/php-ffmpeg", "version": "0.5.1", @@ -4436,17 +4720,17 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "a6a3d39d1f68e3e04555a2d6b43a9df0977d96d9" + "reference": "7de84a436dfddf7d407b9cc6f08afa6fa56ff774" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/8ea5840ad835444627de70a706aabd8c9aa41df8", - "reference": "a6a3d39d1f68e3e04555a2d6b43a9df0977d96d9", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/7de84a436dfddf7d407b9cc6f08afa6fa56ff774", + "reference": "7de84a436dfddf7d407b9cc6f08afa6fa56ff774", "shasum": "" }, "conflict": { "aws/aws-sdk-php": ">=3,<3.2.1", - "cakephp/cakephp": ">=2,<2.4.99|>=1.3,<1.3.18|>=3,<3.0.15|>=2.5,<2.5.90|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3.1,<3.1.3", + "cakephp/cakephp": ">=3,<3.0.15|>=2,<2.4.99|>=2.5,<2.5.90|>=2.6,<2.6.12|>=1.3,<1.3.18|>=2.7,<2.7.6|>=3.1,<3.1.3", "codeigniter/framework": "<3.0.3", "contao/core": ">=2.11,<3|>=3,<3.1|>=3.1,<3.2|>=3.2,<3.2.19|>=3.3,<3.4|>=3.4,<3.4.4", "doctrine/annotations": ">=1,<1.2.7", @@ -4464,6 +4748,7 @@ "friendsofsymfony/user-bundle": ">=1.2,<1.3|>=1.3,<1.3.5", "illuminate/auth": ">=4,<4.0.99|>=4.1,<4.1.26", "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", + "joomla/session": "<1.3.1", "laravel/framework": ">=4,<4.0.99|>=4.1,<4.1.29", "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", "monolog/monolog": ">=1.8,<1.12", @@ -4484,19 +4769,20 @@ "symfony/http-foundation": ">=2,<2.1|>=2.1,<2.2|>=2.2,<2.3|>=2.3,<2.3.27|>=2.4,<2.5|>=2.5,<2.5.11|>=2.6,<2.6.6", "symfony/http-kernel": ">=2,<2.1|>=2.1,<2.2|>=2.2,<2.3|>=2.3,<2.3.29|>=2.4,<2.5|>=2.5,<2.5.12|>=2.6,<2.6.8", "symfony/routing": ">=2,<2.0.19", - "symfony/security": ">=2,<2.0.25|>=2.1,<2.1.13|>=2.2,<2.2.9|>=2.3,<2.3.35|>=2.4,<2.5|>=2.5,<2.6|>=2.6,<2.6.12|>=2.7,<2.7.7", + "symfony/security": ">=2.3,<2.3.37|>=2.4,<2.5|>=2.5,<2.6|>=2.6,<2.6.13|>=2.7,<2.7.9|>=2,<2.0.25|>=2.1,<2.1.13|>=2.2,<2.2.9", + "symfony/security-core": ">=2.4,<2.5|>=2.5,<2.6|>=2.6,<2.6.13|>=2.7,<2.7.9", "symfony/security-http": ">=2.4,<2.5|>=2.5,<2.6|>=2.6,<2.6.12|>=2.7,<2.7.7", "symfony/serializer": ">=2,<2.0.11", - "symfony/symfony": ">=2,<2.1|>=2.1,<2.2|>=2.2,<2.3|>=2.3,<2.3.35|>=2.4,<2.5|>=2.5,<2.6|>=2.6,<2.6.12|>=2.7,<2.7.7", + "symfony/symfony": ">=2,<2.1|>=2.1,<2.2|>=2.2,<2.3|>=2.3,<2.3.37|>=2.4,<2.5|>=2.5,<2.6|>=2.6,<2.6.13|>=2.7,<2.7.9", "symfony/translation": ">=2,<2.0.17", "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", "symfony/web-profiler-bundle": ">=2,<2.1|>=2.1,<2.2|>=2.2,<2.3|>=2.3,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.2|>=2.1.0-beta1,<2.1.3", + "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", "twig/twig": "<1.20", - "typo3/cms": ">=6.2,<6.2.15|>=7,<7.1|>=7.1,<7.2|>=7.2,<7.3|>=7.3,<7.4", - "typo3/flow": ">=1,<1.0.4|>=2.3,<2.3.7|>=3,<3.0.1|>=1.1,<1.1.1|>=2,<2.0.1", + "typo3/cms": ">=6.2,<6.2.16|>=7,<7.1|>=7.1,<7.2|>=7.2,<7.3|>=7.3,<7.4|>=7.4,<7.5|>=7.5,<7.6|>=7.6,<7.6.1", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.7|>=3,<3.0.1", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", "willdurand/js-translation-bundle": "<2.1.1", "yiisoft/yii": ">=1.1.14,<1.1.15", @@ -4541,7 +4827,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2015-11-25 04:45:01" + "time": "2016-01-25 21:27:29" }, { "name": "seld/jsonlint", @@ -5103,25 +5389,492 @@ "time": "2014-12-05 14:17:14" }, { - "name": "symfony/symfony", - "version": "v2.7.7", + "name": "symfony/polyfill-intl-icu", + "version": "v1.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/symfony.git", - "reference": "cc69dbd24b4b2e6de60b2414ef95da2794f459a2" + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "66b0bb4abda229bc073eff6bbc8f2685bdaac165" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/symfony/zipball/cc69dbd24b4b2e6de60b2414ef95da2794f459a2", - "reference": "cc69dbd24b4b2e6de60b2414ef95da2794f459a2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/66b0bb4abda229bc073eff6bbc8f2685bdaac165", + "reference": "66b0bb4abda229bc073eff6bbc8f2685bdaac165", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/intl": "~2.3|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.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": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "1289d16209491b584839022f29257ad859b8532d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d", + "reference": "1289d16209491b584839022f29257ad859b8532d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/polyfill-php54", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php54.git", + "reference": "74663d5a2ff3c530c1bc0571500e0feec9094054" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/74663d5a2ff3c530c1bc0571500e0feec9094054", + "reference": "74663d5a2ff3c530c1bc0571500e0feec9094054", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php54\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "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 backporting some PHP 5.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/polyfill-php55", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php55.git", + "reference": "b4f3f07d91702f8f926339fc4fcf81671d8c27e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/b4f3f07d91702f8f926339fc4fcf81671d8c27e6", + "reference": "b4f3f07d91702f8f926339fc4fcf81671d8c27e6", + "shasum": "" + }, + "require": { + "ircmaxell/password-compat": "~1.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php55\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "4d891fff050101a53a4caabb03277284942d1ad9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/4d891fff050101a53a4caabb03277284942d1ad9", + "reference": "4d891fff050101a53a4caabb03277284942d1ad9", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "8428ceddbbaf102f2906769a8ef2438220c5cb95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/8428ceddbbaf102f2906769a8ef2438220c5cb95", + "reference": "8428ceddbbaf102f2906769a8ef2438220c5cb95", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "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 backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-25 08:44:42" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4", + "reference": "8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "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 utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/security-acl", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-acl.git", + "reference": "4a3f7327ad215242c78f6564ad4ea6d2db1b8347" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-acl/zipball/4a3f7327ad215242c78f6564ad4ea6d2db1b8347", + "reference": "4a3f7327ad215242c78f6564ad4ea6d2db1b8347", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/security-core": "~2.4|~3.0.0" + }, + "require-dev": { + "doctrine/common": "~2.2", + "doctrine/dbal": "~2.2", + "psr/log": "~1.0", + "symfony/phpunit-bridge": "~2.7|~3.0.0" + }, + "suggest": { + "doctrine/dbal": "For using the built-in ACL implementation", + "symfony/class-loader": "For using the ACL generateSql script", + "symfony/finder": "For using the ACL generateSql script" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Acl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - ACL (Access Control List)", + "homepage": "https://symfony.com", + "time": "2015-12-28 09:39:09" + }, + { + "name": "symfony/symfony", + "version": "v2.8.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/symfony.git", + "reference": "f3e6a82bcbea4db3b56df08e491e20a1faae82b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/symfony/zipball/f3e6a82bcbea4db3b56df08e491e20a1faae82b5", + "reference": "f3e6a82bcbea4db3b56df08e491e20a1faae82b5", "shasum": "" }, "require": { "doctrine/common": "~2.4", "php": ">=5.3.9", "psr/log": "~1.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php54": "~1.0", + "symfony/polyfill-php55": "~1.0", + "symfony/polyfill-php56": "~1.0", + "symfony/polyfill-php70": "~1.0", + "symfony/polyfill-util": "~1.0", + "symfony/security-acl": "~2.7", "twig/twig": "~1.23|~2.0" }, + "conflict": { + "phpdocumentor/reflection": "<1.0.7" + }, "replace": { "symfony/asset": "self.version", "symfony/browser-kit": "self.version", @@ -5143,18 +5896,20 @@ "symfony/http-foundation": "self.version", "symfony/http-kernel": "self.version", "symfony/intl": "self.version", + "symfony/ldap": "self.version", "symfony/locale": "self.version", "symfony/monolog-bridge": "self.version", "symfony/options-resolver": "self.version", "symfony/process": "self.version", "symfony/property-access": "self.version", + "symfony/property-info": "self.version", "symfony/proxy-manager-bridge": "self.version", "symfony/routing": "self.version", "symfony/security": "self.version", - "symfony/security-acl": "self.version", "symfony/security-bundle": "self.version", "symfony/security-core": "self.version", "symfony/security-csrf": "self.version", + "symfony/security-guard": "self.version", "symfony/security-http": "self.version", "symfony/serializer": "self.version", "symfony/stopwatch": "self.version", @@ -5174,14 +5929,14 @@ "doctrine/doctrine-bundle": "~1.2", "doctrine/orm": "~2.4,>=2.4.5", "egulias/email-validator": "~1.2", - "ircmaxell/password-compat": "~1.0", "monolog/monolog": "~1.11", - "ocramius/proxy-manager": "~0.4|~1.0" + "ocramius/proxy-manager": "~0.4|~1.0", + "phpdocumentor/reflection": "^1.0.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -5195,12 +5950,8 @@ "Symfony\\Component\\": "src/Symfony/Component/" }, "classmap": [ - "src/Symfony/Component/HttpFoundation/Resources/stubs", "src/Symfony/Component/Intl/Resources/stubs" ], - "files": [ - "src/Symfony/Component/Intl/Resources/stubs/functions.php" - ], "exclude-from-classmap": [ "**/Tests/" ] @@ -5224,7 +5975,7 @@ "keywords": [ "framework" ], - "time": "2015-11-23 11:58:08" + "time": "2016-01-14 12:01:11" }, { "name": "themattharris/tmhoauth", @@ -5430,23 +6181,73 @@ "time": "2013-09-20 10:41:27" }, { - "name": "webmozart/json", - "version": "1.1.0", + "name": "webmozart/assert", + "version": "1.0.2", "source": { "type": "git", - "url": "https://github.com/webmozart/json.git", - "reference": "d44cd8d566f1e2bd2d9b30dc9e4d87d984410058" + "url": "https://github.com/webmozart/assert.git", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/json/zipball/d44cd8d566f1e2bd2d9b30dc9e4d87d984410058", - "reference": "d44cd8d566f1e2bd2d9b30dc9e4d87d984410058", + "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", "shasum": "" }, "require": { - "justinrainbow/json-schema": ">=1.3.7", - "php": ">=5.3.3", - "seld/jsonlint": "~1.0" + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2015-08-24 13:29:44" + }, + { + "name": "webmozart/json", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/webmozart/json.git", + "reference": "a1fb3da904b8364e3db47eed68f76bfb6cd0031a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/json/zipball/a1fb3da904b8364e3db47eed68f76bfb6cd0031a", + "reference": "a1fb3da904b8364e3db47eed68f76bfb6cd0031a", + "shasum": "" + }, + "require": { + "justinrainbow/json-schema": "^1.6", + "php": "^5.3.3|^7.0", + "seld/jsonlint": "^1.0", + "webmozart/path-util": "^2.3" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -5456,7 +6257,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "1.2-dev" } }, "autoload": { @@ -5475,20 +6276,66 @@ } ], "description": "A robust JSON decoder/encoder with support for schema validation.", - "time": "2015-12-11 17:37:16" + "time": "2016-01-14 12:11:46" }, { - "name": "willdurand/negotiation", - "version": "v2.0.1", + "name": "webmozart/path-util", + "version": "2.3.0", "source": { "type": "git", - "url": "https://github.com/willdurand/Negotiation.git", - "reference": "a99d19330bfaa050fd09c3b8a10fc8ac258b9a55" + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/a99d19330bfaa050fd09c3b8a10fc8ac258b9a55", - "reference": "a99d19330bfaa050fd09c3b8a10fc8ac258b9a55", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "time": "2015-12-17 08:42:14" + }, + { + "name": "willdurand/negotiation", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "a8ce6da7acdf07351ccd6a9359c571ebc0725a21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/a8ce6da7acdf07351ccd6a9359c571ebc0725a21", + "reference": "a8ce6da7acdf07351ccd6a9359c571ebc0725a21", "shasum": "" }, "require": { @@ -5524,7 +6371,7 @@ "header", "negotiation" ], - "time": "2015-10-02 12:52:52" + "time": "2015-11-21 14:23:02" }, { "name": "zend/gdata", diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index a8805f1742..711c327036 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -11,15 +11,13 @@ namespace Alchemy\Phrasea; -use Alchemy\Cors\Options\DefaultProvider; -use Alchemy\CorsProvider\CorsServiceProvider; use Alchemy\Geonames\GeonamesServiceProvider; +use Alchemy\Phrasea\Application\Environment; use Alchemy\Phrasea\Application\Helper\AclAware; use Alchemy\Phrasea\Application\Helper\ApplicationBoxAware; use Alchemy\Phrasea\Application\Helper\AuthenticatorAware; +use Alchemy\Phrasea\Application\RouteLoader; use Alchemy\Phrasea\Authorization\AuthorizationServiceProvider; -use Alchemy\Phrasea\Cache\Factory; -use Alchemy\Phrasea\Cache\Manager; use Alchemy\Phrasea\Core\Event\Subscriber\BasketSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\BridgeSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\ExportSubscriber; @@ -29,6 +27,11 @@ use Alchemy\Phrasea\Core\Event\Subscriber\OrderSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaInstallSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\RegistrationSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\ValidationSubscriber; +use Alchemy\Phrasea\Core\MetaProvider\DatabaseMetaProvider; +use Alchemy\Phrasea\Core\MetaProvider\HttpStackMetaProvider; +use Alchemy\Phrasea\Core\MetaProvider\MediaUtilitiesMetaServiceProvider; +use Alchemy\Phrasea\Core\MetaProvider\TemplateEngineMetaProvider; +use Alchemy\Phrasea\Core\MetaProvider\TranslationMetaProvider; use Alchemy\Phrasea\Core\Middleware\ApiApplicationMiddlewareProvider; use Alchemy\Phrasea\Core\Middleware\BasketMiddlewareProvider; use Alchemy\Phrasea\Core\Middleware\TokenMiddlewareProvider; @@ -43,7 +46,6 @@ use Alchemy\Phrasea\Core\Provider\CacheConnectionServiceProvider; use Alchemy\Phrasea\Core\Provider\CacheServiceProvider; use Alchemy\Phrasea\Core\Provider\ConfigurationServiceProvider; use Alchemy\Phrasea\Core\Provider\ConfigurationTesterServiceProvider; -use Alchemy\Phrasea\Core\Provider\ContentNegotiationServiceProvider; use Alchemy\Phrasea\Core\Provider\ConvertersServiceProvider; use Alchemy\Phrasea\Core\Provider\CSVServiceProvider; use Alchemy\Phrasea\Core\Provider\FeedServiceProvider; @@ -54,7 +56,6 @@ use Alchemy\Phrasea\Core\Provider\JMSSerializerServiceProvider; use Alchemy\Phrasea\Core\Provider\LocaleServiceProvider; use Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider; use Alchemy\Phrasea\Core\Provider\NotificationDelivererServiceProvider; -use Alchemy\Phrasea\Core\Provider\ORMServiceProvider; use Alchemy\Phrasea\Core\Provider\PhraseaEventServiceProvider; use Alchemy\Phrasea\Core\Provider\PhraseanetServiceProvider; use Alchemy\Phrasea\Core\Provider\PhraseaVersionServiceProvider; @@ -64,69 +65,45 @@ use Alchemy\Phrasea\Core\Provider\RegistrationServiceProvider; use Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider; use Alchemy\Phrasea\Core\Provider\SearchEngineServiceProvider; use Alchemy\Phrasea\Core\Provider\SerializerServiceProvider; -use Alchemy\Phrasea\Core\Provider\SessionHandlerServiceProvider; use Alchemy\Phrasea\Core\Provider\StatusServiceProvider; use Alchemy\Phrasea\Core\Provider\SubdefServiceProvider; use Alchemy\Phrasea\Core\Provider\TasksServiceProvider; -use Alchemy\Phrasea\Core\Provider\TemporaryFilesystemServiceProvider; use Alchemy\Phrasea\Core\Provider\TokensServiceProvider; -use Alchemy\Phrasea\Core\Provider\TranslationServiceProvider; use Alchemy\Phrasea\Core\Provider\UnicodeServiceProvider; +use Alchemy\Phrasea\Core\Provider\WebhookServiceProvider; use Alchemy\Phrasea\Core\Provider\ZippyServiceProvider; +use Alchemy\Phrasea\Core\Provider\WebProfilerServiceProvider as PhraseaWebProfilerServiceProvider; use Alchemy\Phrasea\Exception\InvalidArgumentException; +use Alchemy\Phrasea\Filesystem\FilesystemServiceProvider; +use Alchemy\Phrasea\Filesystem\ApplicationPathServiceGenerator; use Alchemy\Phrasea\Form\Extension\HelpTypeExtension; +use Alchemy\Phrasea\Media\DatafilesResolver; +use Alchemy\Phrasea\Media\MediaAccessorResolver; +use Alchemy\Phrasea\Media\PermalinkMediaResolver; use Alchemy\Phrasea\Model\Entities\User; -use Alchemy\Phrasea\Twig\BytesConverter; -use Alchemy\Phrasea\Twig\Camelize; -use Alchemy\Phrasea\Twig\Fit; -use Alchemy\Phrasea\Twig\JSUniqueID; -use Alchemy\Phrasea\Twig\PhraseanetExtension; -use Alchemy\Phrasea\Utilities\CachedTranslator; -use Dflydev\Silex\Provider\DoctrineOrm\DoctrineOrmServiceProvider; -use Doctrine\Common\EventManager; use Doctrine\DBAL\Event\ConnectionEventArgs; -use Doctrine\DBAL\Events; -use Doctrine\ORM\Configuration; -use FFMpeg\FFMpegServiceProvider; -use Gedmo\DoctrineExtensions as GedmoExtension; -use MediaAlchemyst\MediaAlchemystServiceProvider; use MediaVorus\Media\MediaInterface; use MediaVorus\MediaVorus; -use MediaVorus\MediaVorusServiceProvider; -use Monolog\Handler\NullHandler; use Monolog\Handler\RotatingFileHandler; -use Monolog\Handler\SyslogHandler; use Monolog\Logger; -use Monolog\Processor\IntrospectionProcessor; -use MP4Box\MP4BoxServiceProvider; use Neutron\ReCaptcha\ReCaptchaServiceProvider; -use Neutron\Silex\Provider\FilesystemServiceProvider; -use Neutron\Silex\Provider\ImagineServiceProvider; -use PHPExiftool\PHPExiftoolServiceProvider; use Silex\Application as SilexApplication; use Silex\Application\TranslationTrait; use Silex\Application\UrlGeneratorTrait; -use Silex\ControllerProviderInterface; -use Silex\Provider\DoctrineServiceProvider; use Silex\Provider\FormServiceProvider; -use Silex\Provider\HttpFragmentServiceProvider; use Silex\Provider\MonologServiceProvider; use Silex\Provider\ServiceControllerServiceProvider; -use Silex\Provider\SessionServiceProvider; use Silex\Provider\SwiftmailerServiceProvider; -use Silex\Provider\TwigServiceProvider; -use Silex\Provider\UrlGeneratorServiceProvider; use Silex\Provider\ValidatorServiceProvider; +use Silex\Provider\WebProfilerServiceProvider; use Sorien\Provider\PimpleDumpProvider; -use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Exception\ExceptionInterface; use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormTypeInterface; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; -use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Unoconv\UnoconvServiceProvider; use XPDF\PdfToText; use XPDF\XPDFServiceProvider; @@ -139,6 +116,10 @@ class Application extends SilexApplication use UrlGeneratorTrait; use TranslationTrait; + const ENV_DEV = 'dev'; + const ENV_PROD = 'prod'; + const ENV_TEST = 'test'; + protected static $availableLanguages = [ 'de' => 'Deutsch', 'en' => 'English', @@ -147,38 +128,31 @@ class Application extends SilexApplication ]; private static $flashTypes = ['warning', 'info', 'success', 'error']; + + /** + * @var Environment + */ private $environment; - const ENV_DEV = 'dev'; - const ENV_PROD = 'prod'; - const ENV_TEST = 'test'; - - public function getEnvironment() + /** + * @param Environment|string $environment + */ + public function __construct($environment = null) { - return $this->environment; - } + if (is_string($environment)) { + $environment = new Environment($environment, false); + } - public function __construct($environment = self::ENV_PROD) - { - parent::__construct(); + $this->environment = $environment ?: new Environment(self::ENV_PROD, false); - error_reporting(-1); - - $this->environment = $environment; + parent::__construct([ + 'debug' => $this->environment->isDebug() + ]); $this->setupCharset(); $this->setupApplicationPaths(); $this->setupConstants(); - $this['debug'] = !in_array($environment, [ - Application::ENV_PROD, - Application::ENV_TEST, - ]); - - if ($this['debug']) { - ini_set('log_errors', 'on'); - ini_set('error_log', $this['root.path'].'/logs/php_error.log'); - } if ('allowed' == getenv('APP_CONTAINER_DUMP')) { $this->register(new PimpleDumpProvider()); } @@ -191,11 +165,9 @@ class Application extends SilexApplication $this->register(new CacheConnectionServiceProvider()); $this->register(new PhraseanetServiceProvider()); $this->register(new ConfigurationTesterServiceProvider()); - $this->register(new DoctrineServiceProvider()); - $this->setupDBAL(); - $this->register(new DoctrineOrmServiceProvider()); - $this->setupOrms(); - $this->register(new ORMServiceProvider()); + + $this->register(new DatabaseMetaProvider()); + $this->register(new BasketMiddlewareProvider()); $this->register(new TokenMiddlewareProvider()); $this->register(new AccountServiceProvider()); @@ -208,25 +180,19 @@ class Application extends SilexApplication $this->register(new ConvertersServiceProvider()); $this->register(new CSVServiceProvider()); $this->register(new RegistrationServiceProvider()); - $this->register(new ImagineServiceProvider()); - $this->setUpImagine(); + $this->register(new JMSSerializerServiceProvider()); - $this->register(new FFMpegServiceProvider()); $this->register(new FeedServiceProvider()); $this->register(new FtpServiceProvider()); $this->register(new GeonamesServiceProvider()); $this->register(new StatusServiceProvider()); $this->setupGeonames(); - $this->register(new MediaAlchemystServiceProvider()); - $this->setupMediaAlchemyst(); - $this->register(new MediaVorusServiceProvider()); - $this->register(new MP4BoxServiceProvider()); $this->register(new NotificationDelivererServiceProvider()); $this->register(new RepositoriesServiceProvider()); $this->register(new ManipulatorServiceProvider()); $this->register(new InstallerServiceProvider()); $this->register(new PhraseaVersionServiceProvider()); - $this->register(new PHPExiftoolServiceProvider()); + $this->register(new RandomGeneratorServiceProvider()); $this->register(new ReCaptchaServiceProvider()); $this->register(new SubdefServiceProvider()); @@ -238,37 +204,23 @@ class Application extends SilexApplication $this->register(new BorderManagerServiceProvider()); } - $this->register(new SessionHandlerServiceProvider()); - $this->register(new SessionServiceProvider(), [ - 'session.test' => $this->getEnvironment() === static::ENV_TEST, - 'session.storage.options' => ['cookie_lifetime' => 0] - ]); - $this->setupSession(); + $this->register(new SerializerServiceProvider()); $this->register(new ServiceControllerServiceProvider()); $this->register(new SwiftmailerServiceProvider()); $this->setupSwiftMailer(); $this->register(new TasksServiceProvider()); - $this->register(new TemporaryFilesystemServiceProvider()); $this->register(new TokensServiceProvider()); - $this->register(new HttpFragmentServiceProvider()); - $this->register(new TwigServiceProvider()); - $this->setupTwig(); - $this->register(new TranslationServiceProvider(), [ - 'locale_fallbacks' => ['fr'], - 'translator.cache-options' => [ - 'debug' => $this['debug'], - 'cache_dir' => $this->share(function($app) { - return $app['cache.path'].'/translations'; - }), - ], - ]); - $this->setupTranslation(); + + $this->register(new TemplateEngineMetaProvider()); + $this->register(new HttpStackMetaProvider()); + $this->register(new MediaUtilitiesMetaServiceProvider()); + $this->register(new TranslationMetaProvider()); + $this->register(new FormServiceProvider()); $this->setupForm(); $this->register(new UnoconvServiceProvider()); - $this->register(new UrlGeneratorServiceProvider()); - $this->setupUrlGenerator(); + $this->register(new UnicodeServiceProvider()); $this->register(new ValidatorServiceProvider()); $this->register(new XPDFServiceProvider()); @@ -277,108 +229,58 @@ class Application extends SilexApplication $this->register(new ManipulatorServiceProvider()); $this->register(new PluginServiceProvider()); $this->register(new PhraseaEventServiceProvider()); - $this->register(new ContentNegotiationServiceProvider()); - $this->register(new CorsServiceProvider(), [ - 'alchemy_cors.debug' => $this['debug'], - 'alchemy_cors.cache_path' => function (Application $app) { - return rtrim($app['cache.path'], '/\\') . '/alchemy_cors.cache.php'; - }, - ]); - $this['phraseanet.api_cors.options_provider'] = function (Application $app) { - $paths = []; - if (isset($app['phraseanet.configuration']['api_cors'])) { - $config = $app['phraseanet.configuration']['api_cors']; - - if (isset($config['enabled']) && $config['enabled']) { - unset($config['enabled']); - - $paths['/api/v\d+/'] = $config; - } - } - - return new DefaultProvider($paths, []); - }; - - $this['alchemy_cors.options_providers'][] = 'phraseanet.api_cors.options_provider'; $this->register(new LocaleServiceProvider()); $this->setupEventDispatcher(); + + $this->register(new WebhookServiceProvider()); + $this['phraseanet.exception_handler'] = $this->share(function ($app) { + /** @var PhraseaExceptionHandler $handler */ $handler = PhraseaExceptionHandler::register($app['debug']); + $handler->setTranslator($app['translator']); $handler->setLogger($app['monolog']); return $handler; }); - $providers = [ - 'Alchemy\Phrasea\ControllerProvider\Admin\Collection' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\ConnectedUsers' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Dashboard' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Databox' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Databoxes' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Feeds' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Fields' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Plugins' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Root' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\SearchEngine' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Setup' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Subdefs' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\TaskManager' => [], - 'Alchemy\Phrasea\ControllerProvider\Admin\Users' => [], - 'Alchemy\Phrasea\ControllerProvider\Client\Root' => [], - 'Alchemy\Phrasea\ControllerProvider\Datafiles' => [], - 'Alchemy\Phrasea\ControllerProvider\Lightbox' => [], - 'Alchemy\Phrasea\ControllerProvider\MediaAccessor' => [], - 'Alchemy\Phrasea\ControllerProvider\Minifier' => [], - 'Alchemy\Phrasea\ControllerProvider\Permalink' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\BasketProvider' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Bridge' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\DoDownload' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Download' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Edit' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Export' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Feed' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Language' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Lazaret' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\MoveCollection' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Order' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Printer' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Property' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Push' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Query' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Record' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Root' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Share' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Story' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Tools' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Tooltip' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\TOU' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\Upload' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\UsrLists' => [], - 'Alchemy\Phrasea\ControllerProvider\Prod\WorkZone' => [], - 'Alchemy\Phrasea\ControllerProvider\Report\Activity' => [], - 'Alchemy\Phrasea\ControllerProvider\Report\Information' => [], - 'Alchemy\Phrasea\ControllerProvider\Report\Root' => [], - 'Alchemy\Phrasea\ControllerProvider\Root\Account' => [], - 'Alchemy\Phrasea\ControllerProvider\Root\Developers' => [], - 'Alchemy\Phrasea\ControllerProvider\Root\Login' => [], - 'Alchemy\Phrasea\ControllerProvider\Root\Root' => [], - 'Alchemy\Phrasea\ControllerProvider\Root\RSSFeeds' => [], - 'Alchemy\Phrasea\ControllerProvider\Root\Session' => [], - 'Alchemy\Phrasea\ControllerProvider\Setup' => [], - 'Alchemy\Phrasea\ControllerProvider\Thesaurus\Thesaurus' => [], - 'Alchemy\Phrasea\ControllerProvider\Thesaurus\Xmlhttp' => [], - 'Alchemy\Phrasea\ControllerProvider\User\Notifications' => [], - 'Alchemy\Phrasea\ControllerProvider\User\Preferences' => [], - 'Alchemy\EmbedProvider\EmbedServiceProvider' => [], - 'Alchemy\EmbedProvider\OembedServiceProvider' => [], - ]; - foreach ($providers as $class => $values) { - $this->register(new $class, $values); + $resolvers = $this['alchemy_embed.resource_resolvers']; + $resolvers['datafile'] = $resolvers->share(function () { + return new DatafilesResolver($this->getApplicationBox()); + }); + + $resolvers['permalinks_permalink'] = $resolvers->share(function () { + return new PermalinkMediaResolver($this->getApplicationBox()); + }); + + $resolvers['media_accessor'] = $resolvers->share(function () { + return new MediaAccessorResolver( + $this->getApplicationBox(), $this['controller.media_accessor'] + ); + }); + + if (self::ENV_DEV === $this->getEnvironment()) { + $this->register($p = new WebProfilerServiceProvider(), [ + 'profiler.cache_dir' => $this['cache.path'].'/profiler', + ]); + + $this->register(new PhraseaWebProfilerServiceProvider()); + $this->mount('/_profiler', $p); + + if ($this['phraseanet.configuration-tester']->isInstalled()) { + $this['db'] = $this->share(function (self $app) { + return $app['orm.em']->getConnection(); + }); + } } } + public function getEnvironment() + { + return $this->environment->getName(); + } + /** * Loads Phraseanet plugins */ @@ -436,113 +338,6 @@ class Application extends SilexApplication return $this->redirect($this->url($route, $parameters)); } - public function setupTwig() - { - $this['twig'] = $this->share( - $this->extend('twig', function (\Twig_Environment $twig, $app) { - $twig->setCache($app['cache.path'].'/twig'); - - $paths = []; - if (file_exists($app['plugin.path'] . '/twig-paths.php')) { - $paths = require $app['plugin.path'] . '/twig-paths.php'; - } - - if ($app['browser']->isTablet() || $app['browser']->isMobile()) { - $paths[] = $app['root.path'] . '/config/templates/mobile'; - $paths[] = $app['root.path'] . '/templates/mobile'; - $paths['phraseanet'] = $app['root.path'] . '/config/templates/mobile'; - $paths['phraseanet'] = $app['root.path'] . '/templates/mobile'; - } - - $paths[] = $app['root.path'] . '/config/templates/web'; - $paths[] = $app['root.path'] . '/templates/web'; - $paths['phraseanet'] = $app['root.path'] . '/config/templates/web'; - $paths['phraseanet'] = $app['root.path'] . '/templates/web'; - - foreach ($paths as $namespace => $path) { - if (!is_int($namespace)) { - $app['twig.loader.filesystem']->addPath($path, $namespace); - } else { - $app['twig.loader.filesystem']->addPath($path); - } - } - - $twig->addGlobal('current_date', new \DateTime()); - - $twig->addExtension(new \Twig_Extension_Core()); - $twig->addExtension(new \Twig_Extension_Optimizer()); - $twig->addExtension(new \Twig_Extension_Escaper()); - if ($app['debug']) { - $twig->addExtension(new \Twig_Extension_Debug()); - } - - // add filter trans - $twig->addExtension(new TranslationExtension($app['translator'])); - // add filter localizeddate - $twig->addExtension(new \Twig_Extensions_Extension_Intl()); - // add filters truncate, wordwrap, nl2br - $twig->addExtension(new \Twig_Extensions_Extension_Text()); - $twig->addExtension(new JSUniqueID()); - $twig->addExtension(new Fit()); - $twig->addExtension(new Camelize()); - $twig->addExtension(new BytesConverter()); - $twig->addExtension(new PhraseanetExtension($app)); - - $twig->addFilter('serialize', new \Twig_Filter_Function('serialize')); - $twig->addFilter('stristr', new \Twig_Filter_Function('stristr')); - $twig->addFilter('get_class', new \Twig_Filter_Function('get_class')); - $twig->addFilter('stripdoublequotes', new \Twig_Filter_Function('stripdoublequotes')); - $twig->addFilter('get_collection_logo', new \Twig_Filter_Function('collection::getLogo')); - $twig->addFilter('floor', new \Twig_Filter_Function('floor')); - $twig->addFilter('ceil', new \Twig_Filter_Function('ceil')); - $twig->addFilter('max', new \Twig_Filter_Function('max')); - $twig->addFilter('min', new \Twig_Filter_Function('min')); - $twig->addFilter('bas_labels', new \Twig_Filter_Function('phrasea::bas_labels')); - $twig->addFilter('sbas_names', new \Twig_Filter_Function('phrasea::sbas_names')); - $twig->addFilter('sbas_labels', new \Twig_Filter_Function('phrasea::sbas_labels')); - $twig->addFilter('sbas_from_bas', new \Twig_Filter_Function('phrasea::sbasFromBas')); - $twig->addFilter('key_exists', new \Twig_Filter_Function('array_key_exists')); - $twig->addFilter('round', new \Twig_Filter_Function('round')); - $twig->addFilter('count', new \Twig_Filter_Function('count')); - $twig->addFilter('formatOctets', new \Twig_Filter_Function('p4string::format_octets')); - $twig->addFilter('base_from_coll', new \Twig_Filter_Function('phrasea::baseFromColl')); - $twig->addFilter(new \Twig_SimpleFilter('escapeSimpleQuote', function ($value) { - return str_replace("'", "\\'", $value); - })); - - $twig->addFilter(new \Twig_SimpleFilter('highlight', function (\Twig_Environment $twig, $string) { - return str_replace(['[[em]]', '[[/em]]'], ['', ''], $string); - }, ['needs_environment' => true,'is_safe' => ['html']])); - - $twig->addFilter(new \Twig_SimpleFilter('linkify', function (\Twig_Environment $twig, $string) { - return preg_replace( - "(([^']{1})((https?|file):((/{2,4})|(\\{2,4}))[\w:#%/;$()~_?/\-=\\\.&]*)([^']{1}))" - , '$1 $2  $7' - , $string - ); - }, ['needs_environment' => true, 'is_safe' => ['html']])); - - $twig->addFilter(new \Twig_SimpleFilter('bounce', function (\Twig_Environment $twig, $fieldValue, $fieldName, $searchRequest, $sbasId) { - // bounce value if it is present in thesaurus as well - return "" - . $fieldValue - . ""; - - }, ['needs_environment' => true, 'is_safe' => ['html']])); - - $twig->addFilter(new \Twig_SimpleFilter('escapeDoubleQuote', function ($value) { - return str_replace('"', '\"', $value); - })); - - return $twig; - }) - ); - } - /** * Adds a flash message for type. * @@ -619,6 +414,14 @@ class Application extends SilexApplication return $this; } + /** + * @return bool + */ + public function isDebug() + { + return $this->environment->isDebug(); + } + /** * Returns true if a captcha is required for next authentication * @@ -674,75 +477,11 @@ class Application extends SilexApplication */ public function bindRoutes() { - $providers = [ - '/account/' => 'Alchemy\Phrasea\ControllerProvider\Root\Account', - '/admin/' => 'Alchemy\Phrasea\ControllerProvider\Admin\Root', - '/admin/collection' => 'Alchemy\Phrasea\ControllerProvider\Admin\Collection', - '/admin/connected-users' => 'Alchemy\Phrasea\ControllerProvider\Admin\ConnectedUsers', - '/admin/dashboard' => 'Alchemy\Phrasea\ControllerProvider\Admin\Dashboard', - '/admin/databox' => 'Alchemy\Phrasea\ControllerProvider\Admin\Databox', - '/admin/databoxes' => 'Alchemy\Phrasea\ControllerProvider\Admin\Databoxes', - '/admin/fields' => 'Alchemy\Phrasea\ControllerProvider\Admin\Fields', - '/admin/publications' => 'Alchemy\Phrasea\ControllerProvider\Admin\Feeds', - '/admin/plugins' => 'Alchemy\Phrasea\ControllerProvider\Admin\Plugins', - '/admin/search-engine' => 'Alchemy\Phrasea\ControllerProvider\Admin\SearchEngine', - '/admin/setup' => 'Alchemy\Phrasea\ControllerProvider\Admin\Setup', - '/admin/subdefs' => 'Alchemy\Phrasea\ControllerProvider\Admin\Subdefs', - '/admin/task-manager' => 'Alchemy\Phrasea\ControllerProvider\Admin\TaskManager', - '/admin/users' => 'Alchemy\Phrasea\ControllerProvider\Admin\Users', - '/client/' => 'Alchemy\Phrasea\ControllerProvider\Client\Root', - '/datafiles' => 'Alchemy\Phrasea\ControllerProvider\Datafiles', - '/developers/' => 'Alchemy\Phrasea\ControllerProvider\Root\Developers', - '/download/' => 'Alchemy\Phrasea\ControllerProvider\Prod\DoDownload', - '/embed/' => 'Alchemy\EmbedProvider\EmbedServiceProvider', - '/oembed/' => 'Alchemy\EmbedProvider\OembedServiceProvider', - '/feeds/' => 'Alchemy\Phrasea\ControllerProvider\Root\RSSFeeds', - '/include/minify' => 'Alchemy\Phrasea\ControllerProvider\Minifier', - '/login/' => 'Alchemy\Phrasea\ControllerProvider\Root\Login', - '/lightbox' => 'Alchemy\Phrasea\ControllerProvider\Lightbox', - '/permalink' => 'Alchemy\Phrasea\ControllerProvider\Permalink', - '/prod/baskets' => 'Alchemy\Phrasea\ControllerProvider\Prod\BasketProvider', - '/prod/bridge/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Bridge', - '/prod/download' => 'Alchemy\Phrasea\ControllerProvider\Prod\Download', - '/prod/export/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Export', - '/prod/feeds' => 'Alchemy\Phrasea\ControllerProvider\Prod\Feed', - '/prod/language' => 'Alchemy\Phrasea\ControllerProvider\Prod\Language', - '/prod/lazaret/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Lazaret', - '/prod/lists' => 'Alchemy\Phrasea\ControllerProvider\Prod\UsrLists', - '/prod/order/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Order', - '/prod/printer/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Printer', - '/prod/push/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Push', - '/prod/query/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Query', - '/prod/records/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Record', - '/prod/records/edit' => 'Alchemy\Phrasea\ControllerProvider\Prod\Edit', - '/prod/records/movecollection' => 'Alchemy\Phrasea\ControllerProvider\Prod\MoveCollection', - '/prod/records/property' => 'Alchemy\Phrasea\ControllerProvider\Prod\Property', - '/prod/share/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Share', - '/prod/story' => 'Alchemy\Phrasea\ControllerProvider\Prod\Story', - '/prod/tools/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Tools', - '/prod/tooltip' => 'Alchemy\Phrasea\ControllerProvider\Prod\Tooltip', - '/prod/TOU/' => 'Alchemy\Phrasea\ControllerProvider\Prod\TOU', - '/prod/upload/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Upload', - '/prod/WorkZone' => 'Alchemy\Phrasea\ControllerProvider\Prod\WorkZone', - '/prod/' => 'Alchemy\Phrasea\ControllerProvider\Prod\Root', - '/report/activity' => 'Alchemy\Phrasea\ControllerProvider\Report\Activity', - '/report/informations' => 'Alchemy\Phrasea\ControllerProvider\Report\Information', - '/report/' => 'Alchemy\Phrasea\ControllerProvider\Report\Root', - '/session/' => 'Alchemy\Phrasea\ControllerProvider\Root\Session', - '/setup' => 'Alchemy\Phrasea\ControllerProvider\Setup', - '/thesaurus' => 'Alchemy\Phrasea\ControllerProvider\Thesaurus\Thesaurus', - '/user/notifications/' => 'Alchemy\Phrasea\ControllerProvider\User\Notifications', - '/user/preferences/' => 'Alchemy\Phrasea\ControllerProvider\User\Preferences', - '/xmlhttp' => 'Alchemy\Phrasea\ControllerProvider\Thesaurus\Xmlhttp', - '/' => 'Alchemy\Phrasea\ControllerProvider\Root\Root', - ]; + $loader = new RouteLoader(); - // controllers with routes referenced by api - $providers[$this['controller.media_accessor.route_prefix']] = 'Alchemy\Phrasea\ControllerProvider\MediaAccessor'; - foreach ($providers as $prefix => $class) { - $this->mount($prefix, new $class); - } + $loader->registerProviders(RouteLoader::$defaultProviders); + $loader->bindRoutes($this); $this->bindPluginRoutes('plugin.controller_providers.root'); } @@ -786,86 +525,53 @@ class Application extends SilexApplication // app root path $this['root.path'] = realpath(__DIR__ . '/../../..'); // temporary resources default path such as download zip, quarantined documents etc .. - $this['tmp.path'] = $this['root.path'].'/tmp'; + $this['tmp.path'] = getenv('PHRASEANET_TMP') ?: $this['root.path'].'/tmp'; // plugin path - $this['plugin.path'] = $dir = $this['root.path'].'/plugins'; + $this['plugin.path'] = $this['root.path'].'/plugins'; // thumbnails path - $this['thumbnail.path'] = $dir = $this['root.path'].'/www/thumbnails'; + $this['thumbnail.path'] = $this['root.path'].'/www/thumbnails'; - // cache path (twig, minify, translations, configuration, doctrine metas serializer metas, profiler etc ...) - $this['cache.path'] = $this->share(function() { - $defaultPath = $path = $this['root.path'].'/cache'; - if ($this['phraseanet.configuration']->isSetup()) { - $path = $this['conf']->get(['main', 'storage', 'cache'], $path); + $factory = new ApplicationPathServiceGenerator(); + + $this['cache.path'] = $factory->createDefinition( + ['main', 'storage', 'cache'], + function (Application $app) { + return $app['root.path'].'/cache'; } - $path = $path ?: $defaultPath; - - // ensure path is created - $this['filesystem']->mkdir($path); - - return $path; - }); + ); $this['cache.paths'] = function (Application $app) { return new \ArrayObject([ $app['cache.path'], ]); }; - // log path - $this['log.path'] = $this->share(function() { - $defaultPath = $path = $this['root.path'].'/logs'; - if ($this['phraseanet.configuration']->isSetup()) { - return $this['conf']->get(['main', 'storage', 'log'], $path); + $this['log.path'] = $factory->createDefinition( + ['main', 'storage', 'log'], + function (Application $app) { + return $app['root.path'].'/logs'; } - $path = $path ?: $defaultPath; + ); - // ensure path is created - $this['filesystem']->mkdir($path); - - return $path; - }); - - // temporary download file path (zip file) - $this['tmp.download.path'] = $this->share(function() { - $defaultPath = $path = $this['tmp.path'].'/download'; - if ($this['phraseanet.configuration']->isSetup()) { - return $this['conf']->get(['main', 'storage', 'download'], $path); + $this['tmp.download.path'] = $factory->createDefinition( + ['main', 'storage', 'download'], + function (Application $app) { + return $app['tmp.path'].'/download'; } - $path = $path ?: $defaultPath; + ); - // ensure path is created - $this['filesystem']->mkdir($path); - - return $path; - }); - - // quarantined file path - $this['tmp.lazaret.path'] = $this->share(function() { - $defaultPath = $path = $this['tmp.path'].'/lazaret'; - if ($this['phraseanet.configuration']->isSetup()) { - return $this['conf']->get(['main', 'storage', 'quarantine'], $path); + $this['tmp.lazaret.path'] = $factory->createDefinition( + ['main', 'storage', 'quarantine'], + function (Application $app) { + return $app['tmp.path'].'/lazaret'; } - $path = $path ?: $defaultPath; + ); - // ensure path is created - $this['filesystem']->mkdir($path); - - return $path; - }); - - // document caption file path - $this['tmp.caption.path'] = $this->share(function() { - $defaultPath = $path = $this['tmp.path'].'/caption'; - if ($this['phraseanet.configuration']->isSetup()) { - return $this['conf']->get(['main', 'storage', 'caption'], $path); + $this['tmp.caption.path'] = $factory->createDefinition( + ['main', 'storage', 'caption'], + function (Application $app) { + return $app['tmp.path'].'/caption'; } - $path = $path ?: $defaultPath; - - // ensure path is created - $this['filesystem']->mkdir($path); - - return $path; - }); + ); } @@ -891,98 +597,6 @@ class Application extends SilexApplication })); } - private function setUpImagine() - { - $this['imagine.factory'] = $this->share(function (Application $app) { - if ($app['conf']->get(['registry', 'executables', 'imagine-driver']) != '') { - return $app['conf']->get(['registry', 'executables', 'imagine-driver']); - } - if (class_exists('\Gmagick')) { - return 'gmagick'; - } - if (class_exists('\Imagick')) { - return 'imagick'; - } - if (extension_loaded('gd')) { - return 'gd'; - } - - throw new \RuntimeException('No Imagine driver available'); - }); - } - - private function setupTranslation() - { - $this['translator'] = $this->share($this->extend('translator', function (CachedTranslator $translator, Application $app) { - $translator->addResource('xlf', __DIR__.'/../../../resources/locales/messages.fr.xlf', 'fr', 'messages'); - $translator->addResource('xlf', __DIR__.'/../../../resources/locales/validators.fr.xlf', 'fr', 'validators'); - $translator->addResource('xlf', __DIR__.'/../../../resources/locales/messages.en.xlf', 'en', 'messages'); - $translator->addResource('xlf', __DIR__.'/../../../resources/locales/validators.en.xlf', 'en', 'validators'); - $translator->addResource('xlf', __DIR__.'/../../../resources/locales/messages.de.xlf', 'de', 'messages'); - $translator->addResource('xlf', __DIR__.'/../../../resources/locales/validators.de.xlf', 'de', 'validators'); - $translator->addResource('xlf', __DIR__.'/../../../resources/locales/messages.nl.xlf', 'nl', 'messages'); - $translator->addResource('xlf', __DIR__.'/../../../resources/locales/validators.nl.xlf', 'nl', 'validators'); - - return $translator; - })); - } - - private function setupOrms() - { - $app = $this; - - // Override "orm.cache.configurer" service provided for benefiting - // of "phraseanet.cache-service" - $app['orm.cache.configurer'] = $app->protect(function($name, Configuration $config, $options) use ($app) { - /** @var Manager $service */ - $service = $app['phraseanet.cache-service']; - $config->setMetadataCacheImpl( - $service->factory('ORM_metadata', $app['orm.cache.driver'], $app['orm.cache.options']) - ); - $config->setQueryCacheImpl( - $service->factory('ORM_query', $app['orm.cache.driver'], $app['orm.cache.options']) - ); - $config->setResultCacheImpl( - $service->factory('ORM_result', $app['orm.cache.driver'], $app['orm.cache.options']) - ); - $config->setHydrationCacheImpl( - $service->factory('ORM_hydration', $app['orm.cache.driver'], $app['orm.cache.options']) - ); - }); - $app['orm.proxies_dir'] = $app['root.path'].'/resources/proxies'; - $app['orm.auto_generate_proxies'] = $app['debug']; - $app['orm.proxies_namespace'] = 'Alchemy\Phrasea\Model\Proxies'; - - $this['orm.ems'] = $this->share($this->extend('orm.ems', function (\Pimple $ems, $app) { - GedmoExtension::registerAnnotations(); - - foreach ($ems->keys() as $key) { - $app['orm.annotation.register']($key); - $connection = $ems[$key]->getConnection(); - - $app['connection.pool.manager']->add($connection); - - $types = $app['orm.ems.options'][$key]['types']; - $app['dbal.type.register']($connection, $types); - } - - return $ems; - })); - } - - private function setupSession() - { - $this['session.storage.test'] = $this->share(function (Application $app) { - return new MockArraySessionStorage(); - }); - - $this['session.storage.handler'] = $this->share(function (Application $app) { - if (!$this['phraseanet.configuration-tester']->isInstalled()) { - return new NullSessionHandler(); - } - return $this['session.storage.handler.factory']->create($app['conf']); - }); - } private function setupRecaptacha() { $this['recaptcha.public-key'] = $this->share(function (Application $app) { @@ -1000,38 +614,10 @@ class Application extends SilexApplication private function setupGeonames() { $this['geonames.server-uri'] = $this->share(function (Application $app) { - return $app['conf']->get(['registry', 'webservices', 'geonames-server'], 'http://geonames.alchemyasp.com/'); }); } - private function setupDBAL() - { - $this['dbs.config'] = $this->share($this->extend('dbs.config', function ($configs, $app) { - if ($app->getEnvironment() !== self::ENV_DEV) { - return $configs; - } - - foreach($configs->keys() as $service) { - $app['dbal.config.register.loggers']($configs[$service]); - } - - return $configs; - })); - - $this['dbs.event_manager'] = $this->share($this->extend('dbs.event_manager', function ($eventManagers, $app) { - foreach ($eventManagers->keys() as $name) { - /** @var EventManager $eventManager */ - $eventManager = $eventManagers[$name]; - $app['dbal.evm.register.listeners']($eventManager); - - $eventManager->addEventListener(Events::postConnect, $this); - } - - return $eventManagers; - })); - } - /** * @param ConnectionEventArgs $args * @throws \Doctrine\DBAL\DBALException @@ -1043,60 +629,6 @@ class Application extends SilexApplication } } - private function setupMediaAlchemyst() - { - $this['media-alchemyst.configuration'] = $this->share(function (Application $app) { - $configuration = []; - - foreach ([ - 'swftools.pdf2swf.binaries' => 'pdf2swf_binary', - 'swftools.swfrender.binaries' => 'swf_render_binary', - 'swftools.swfextract.binaries' => 'swf_extract_binary', - 'unoconv.binaries' => 'unoconv_binary', - 'mp4box.binaries' => 'mp4box_binary', - 'gs.binaries' => 'ghostscript_binary', - 'ffmpeg.ffmpeg.binaries' => 'ffmpeg_binary', - 'ffmpeg.ffprobe.binaries' => 'ffprobe_binary', - 'ffmpeg.ffmpeg.timeout' => 'ffmpeg_timeout', - 'ffmpeg.ffprobe.timeout' => 'ffprobe_timeout', - 'gs.timeout' => 'gs_timeout', - 'mp4box.timeout' => 'mp4box_timeout', - 'swftools.timeout' => 'swftools_timeout', - 'unoconv.timeout' => 'unoconv_timeout', - ] as $parameter => $key) { - if ($this['conf']->has(['main', 'binaries', $key])) { - $configuration[$parameter] = $this['conf']->get(['main', 'binaries', $key]); - } - } - - $configuration['ffmpeg.threads'] = $app['conf']->get(['registry', 'executables', 'ffmpeg-threads']) ?: null; - $configuration['imagine.driver'] = $app['conf']->get(['registry', 'executables', 'imagine-driver']) ?: null; - - return $configuration; - }); - $this['media-alchemyst.logger'] = $this->share(function (Application $app) { - return $app['monolog']; - }); - } - - private function setupUrlGenerator() - { - $this['url_generator'] = $this->share($this->extend('url_generator', function ($urlGenerator, Application $app) { - if ($app['configuration.store']->isSetup()) { - $data = parse_url($app['conf']->get('servername')); - - if (isset($data['scheme'])) { - $urlGenerator->getContext()->setScheme($data['scheme']); - } - if (isset($data['host'])) { - $urlGenerator->getContext()->setHost($data['host']); - } - } - - return $urlGenerator; - })); - } - private function setupSwiftMailer() { $this['swiftmailer.transport'] = $this->share(function (Application $app) { @@ -1161,8 +693,7 @@ class Application extends SilexApplication private function setupEventDispatcher() { $this['dispatcher'] = $this->share( - $this->extend('dispatcher', function ($dispatcher, Application $app) { - //$dispatcher->addListener(KernelEvents::RESPONSE, [$app, 'addUTF8Charset'], -128); + $this->extend('dispatcher', function (EventDispatcherInterface $dispatcher, Application $app) { $dispatcher->addSubscriber($app['phraseanet.logout-subscriber']); $dispatcher->addSubscriber($app['phraseanet.locale-subscriber']); $dispatcher->addSubscriber($app['phraseanet.content-negotiation-subscriber']); @@ -1190,12 +721,15 @@ class Application extends SilexApplication if (!defined('JETON_MAKE_SUBDEF')) { define('JETON_MAKE_SUBDEF', 0x01); } + if (!defined('JETON_WRITE_META_DOC')) { define('JETON_WRITE_META_DOC', 0x02); } + if (!defined('JETON_WRITE_META_SUBDEF')) { define('JETON_WRITE_META_SUBDEF', 0x04); } + if (!defined('JETON_WRITE_META')) { define('JETON_WRITE_META', 0x06); } @@ -1212,30 +746,8 @@ class Application extends SilexApplication */ public function bindPluginRoutes($routeParameter) { - foreach ($this[$routeParameter] as $provider) { - $prefix = ''; + $loader = new RouteLoader(); - if (is_array($provider)) { - $providerDefinition = $provider; - list($prefix, $provider) = $providerDefinition; - } - - if (!is_string($prefix) || !is_string($provider)) { - continue; - } - - $prefix = '/' . ltrim($prefix, '/'); - if (!isset($this[$provider])) { - continue; - } - - $provider = $this[$provider]; - - if (!$provider instanceof ControllerProviderInterface) { - continue; - } - - $this->mount($prefix, $provider); - } + $loader->bindPluginRoutes($this, $routeParameter); } } diff --git a/lib/Alchemy/Phrasea/Application/Api.php b/lib/Alchemy/Phrasea/Application/Api.php index cb8ca88444..29d68930b4 100644 --- a/lib/Alchemy/Phrasea/Application/Api.php +++ b/lib/Alchemy/Phrasea/Application/Api.php @@ -52,10 +52,10 @@ return call_user_func(function ($environment = PhraseaApplication::ENV_PROD) { })); $app['phraseanet.content-negotiation.priorities'] = array_merge( + ['application/json', 'application/yaml', 'text/yaml', 'text/javascript', 'application/javascript'], V1::$extendedContentTypes['json'], V1::$extendedContentTypes['jsonp'], - V1::$extendedContentTypes['yaml'], - ['application/json', 'application/yaml', 'text/yaml', 'text/javascript', 'application/javascript'] + V1::$extendedContentTypes['yaml'] ); $app['phraseanet.content-negotiation.custom_formats'] = [ diff --git a/lib/Alchemy/Phrasea/Application/ApplicationLoader.php b/lib/Alchemy/Phrasea/Application/ApplicationLoader.php new file mode 100644 index 0000000000..6664bf044a --- /dev/null +++ b/lib/Alchemy/Phrasea/Application/ApplicationLoader.php @@ -0,0 +1,56 @@ +register(new SetupMiddlewareProvider()); + $app->loadPlugins(); + + $app['exception_handler'] = $app->share(function ($app) { + return new PhraseaExceptionHandlerSubscriber($app['phraseanet.exception_handler']); + }); + + $app['monolog'] = $app->share($app->extend('monolog', function (Logger $monolog) { + $monolog->pushProcessor(new WebProcessor()); + + return $monolog; + })); + + $app->before($app['setup.validate-config'], Application::EARLY_EVENT); + $app->bindRoutes(); + + $app['dispatcher'] = $app->share( + $app->extend('dispatcher', function (EventDispatcherInterface $dispatcher, Application $app) { + $dispatcher->addSubscriber(new BridgeExceptionSubscriber($app)); + $dispatcher->addSubscriber(new FirewallSubscriber()); + $dispatcher->addSubscriber(new JsonRequestSubscriber()); + + if ($app->isDebug()){ + $dispatcher->addSubscriber(new DebuggerSubscriber($app)); + } + + return $dispatcher; + }) + ); + + return $app; + } +} diff --git a/lib/Alchemy/Phrasea/Application/Environment.php b/lib/Alchemy/Phrasea/Application/Environment.php new file mode 100644 index 0000000000..6c3825c368 --- /dev/null +++ b/lib/Alchemy/Phrasea/Application/Environment.php @@ -0,0 +1,42 @@ +name = (string) $name; + $this->debug = ((bool) $debug) || ! in_array($name, [ + Application::ENV_PROD, Application::ENV_TEST + ]); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return bool + */ + public function isDebug() + { + return $this->debug; + } +} diff --git a/lib/Alchemy/Phrasea/Application/Helper/ApplicationBoxAware.php b/lib/Alchemy/Phrasea/Application/Helper/ApplicationBoxAware.php index 4d75a46705..50c917ae8d 100644 --- a/lib/Alchemy/Phrasea/Application/Helper/ApplicationBoxAware.php +++ b/lib/Alchemy/Phrasea/Application/Helper/ApplicationBoxAware.php @@ -9,6 +9,8 @@ */ namespace Alchemy\Phrasea\Application\Helper; +use Alchemy\Phrasea\Collection\CollectionService; + trait ApplicationBoxAware { /** @var \appbox|callable */ @@ -61,6 +63,14 @@ trait ApplicationBoxAware return $this->applicationBox; } + /** + * @return CollectionService + */ + public function getCollectionService() + { + return $this['services.collection']; + } + /** * Find a registered Databoxes. * diff --git a/lib/Alchemy/Phrasea/Application/Helper/DelivererAware.php b/lib/Alchemy/Phrasea/Application/Helper/DelivererAware.php index f5d3f68dce..06803947c7 100644 --- a/lib/Alchemy/Phrasea/Application/Helper/DelivererAware.php +++ b/lib/Alchemy/Phrasea/Application/Helper/DelivererAware.php @@ -10,6 +10,7 @@ namespace Alchemy\Phrasea\Application\Helper; use Alchemy\Phrasea\Http\DeliverDataInterface; +use Symfony\Component\HttpFoundation\Response; trait DelivererAware { @@ -57,28 +58,15 @@ trait DelivererAware /** * Returns a HTTP Response ready to deliver a binary file * - * @param string $file - * @param string $filename - * @param string $disposition + * @param string $file + * @param string $filename + * @param string $disposition * @param string|null $mimeType - * @param integer $cacheDuration + * @param integer $cacheDuration + * @return Response */ public function deliverFile($file, $filename = null, $disposition = DeliverDataInterface::DISPOSITION_INLINE, $mimeType = null, $cacheDuration = null) { return $this->getDeliverer()->deliverFile($file, $filename, $disposition, $mimeType, $cacheDuration); } - - /** - * Return a HTTP Response ready to deliver data - * - * @param string $data - * @param string $filename - * @param string $mimeType - * @param string $disposition - * @param integer $cacheDuration - */ - public function deliverData($data, $filename, $mimeType, $disposition = DeliverDataInterface::DISPOSITION_INLINE, $cacheDuration = null) - { - return $this->getDeliverer()->deliverData($data, $filename, $disposition, $mimeType, $cacheDuration); - } } diff --git a/lib/Alchemy/Phrasea/Application/Helper/FilesystemAware.php b/lib/Alchemy/Phrasea/Application/Helper/FilesystemAware.php index 0112b60cb2..213b356f31 100644 --- a/lib/Alchemy/Phrasea/Application/Helper/FilesystemAware.php +++ b/lib/Alchemy/Phrasea/Application/Helper/FilesystemAware.php @@ -10,7 +10,6 @@ namespace Alchemy\Phrasea\Application\Helper; use Neutron\TemporaryFilesystem\Manager; -use Neutron\TemporaryFilesystem\TemporaryFilesystemInterface; use Symfony\Component\Filesystem\Filesystem; trait FilesystemAware diff --git a/lib/Alchemy/Phrasea/Application/Root.php b/lib/Alchemy/Phrasea/Application/Root.php index 64181dc9c6..619dcae2e0 100644 --- a/lib/Alchemy/Phrasea/Application/Root.php +++ b/lib/Alchemy/Phrasea/Application/Root.php @@ -9,80 +9,9 @@ * file that was distributed with this source code. */ -namespace Alchemy\Phrasea\Application; - -use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Application; -use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaExceptionHandlerSubscriber; -use Alchemy\Phrasea\Core\Event\Subscriber\BridgeExceptionSubscriber; -use Alchemy\Phrasea\Core\Event\Subscriber\FirewallSubscriber; -use Alchemy\Phrasea\Core\Event\Subscriber\JsonRequestSubscriber; -use Alchemy\Phrasea\Core\Event\Subscriber\DebuggerSubscriber; -use Monolog\Logger; -use Monolog\Processor\WebProcessor; -use Silex\Provider\WebProfilerServiceProvider; -use Sorien\Provider\DoctrineProfilerServiceProvider; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpFoundation\Request; -return call_user_func(function ($environment = PhraseaApplication::ENV_PROD) { - $app = new PhraseaApplication($environment); - $app->loadPlugins(); - - $app['exception_handler'] = $app->share(function ($app) { - return new PhraseaExceptionHandlerSubscriber($app['phraseanet.exception_handler']); - }); - $app['monolog'] = $app->share($app->extend('monolog', function (Logger $monolog) { - $monolog->pushProcessor(new WebProcessor()); - - return $monolog; - })); - - $app->before(function (Request $request) use ($app) { - - if (0 === strpos($request->getPathInfo(), '/setup')) { - if (!$app['phraseanet.configuration-tester']->isInstalled()) { - if (!$app['phraseanet.configuration-tester']->isBlank()) { - if ('setup_upgrade_instructions' !== $app['request']->attributes->get('_route')) { - return $app->redirectPath('setup_upgrade_instructions'); - } - } - } elseif (!$app['phraseanet.configuration-tester']->isBlank()) { - return $app->redirectPath('homepage'); - } - } else { - if (false === strpos($request->getPathInfo(), '/include/minify')) { - $app['firewall']->requireSetup(); - } - } - }, Application::EARLY_EVENT); - - $app->bindRoutes(); - - if (PhraseaApplication::ENV_DEV === $app->getEnvironment()) { - $app->register($p = new WebProfilerServiceProvider(), [ - 'profiler.cache_dir' => $app['cache.path'].'/profiler', - ]); - $app->mount('/_profiler', $p); - - if ($app['phraseanet.configuration-tester']->isInstalled()) { - $app->register(new DoctrineProfilerServiceProvider()); - $app['db'] = $app->share(function (PhraseaApplication $app) { - return $app['orm.em']->getConnection(); - }); - } - } - - $app['dispatcher'] = $app->share( - $app->extend('dispatcher', function (EventDispatcherInterface $dispatcher, PhraseaApplication $app) { - $dispatcher->addSubscriber(new BridgeExceptionSubscriber($app)); - $dispatcher->addSubscriber(new FirewallSubscriber()); - $dispatcher->addSubscriber(new JsonRequestSubscriber()); - $dispatcher->addSubscriber(new DebuggerSubscriber($app)); - - return $dispatcher; - }) - ); - - return $app; -}, isset($environment) ? $environment : PhraseaApplication::ENV_PROD); +return (new Application\ApplicationLoader())->buildWebApplication( + isset($environment) ? $environment : Application::ENV_PROD, + isset($forceDebug) ? $forceDebug : false +); diff --git a/lib/Alchemy/Phrasea/Application/RouteLoader.php b/lib/Alchemy/Phrasea/Application/RouteLoader.php new file mode 100644 index 0000000000..c4e9f5ac55 --- /dev/null +++ b/lib/Alchemy/Phrasea/Application/RouteLoader.php @@ -0,0 +1,155 @@ + Providers\Root\Account::class, + '/admin/' => Providers\Admin\Root::class, + '/admin/collection' => Providers\Admin\Collection::class, + '/admin/connected-users' => Providers\Admin\ConnectedUsers::class, + '/admin/dashboard' => Providers\Admin\Dashboard::class, + '/admin/databox' => Providers\Admin\Databox::class, + '/admin/databoxes' => Providers\Admin\Databoxes::class, + '/admin/fields' => Providers\Admin\Fields::class , + '/admin/publications' => Providers\Admin\Feeds::class, + '/admin/plugins' => Providers\Admin\Plugins::class, + '/admin/search-engine' => Providers\Admin\SearchEngine::class, + '/admin/setup' => Providers\Admin\Setup::class, + '/admin/subdefs' => Providers\Admin\Subdefs::class, + '/admin/task-manager' => Providers\Admin\TaskManager::class, + '/admin/users' => Providers\Admin\Users::class, + '/client/' => Providers\Client\Root::class, + '/datafiles' => Providers\Datafiles::class, + '/developers/' => Providers\Root\Developers::class, + '/download/' => Providers\Prod\DoDownload::class, + '/embed/' => EmbedServiceProvider::class, + '/feeds/' => Providers\Root\RSSFeeds::class, + '/include/minify' => Providers\Minifier::class, + '/login/' => Providers\Root\Login::class, + '/lightbox' => Providers\Lightbox::class, + '/permalink' => Providers\Permalink::class, + '/prod/baskets' => Providers\Prod\BasketProvider::class, + '/prod/bridge/' => Providers\Prod\Bridge::class, + '/prod/download' => Providers\Prod\Download::class, + '/prod/export/' => Providers\Prod\Export::class, + '/prod/feeds' => Providers\Prod\Feed::class, + '/prod/language' => Providers\Prod\Language::class, + '/prod/lazaret/' => Providers\Prod\Lazaret::class, + '/prod/lists' => Providers\Prod\UsrLists::class, + '/prod/order/' => Providers\Prod\Order::class, + '/prod/printer/' => Providers\Prod\Printer::class, + '/prod/push/' => Providers\Prod\Push::class, + '/prod/query/' => Providers\Prod\Query::class, + '/prod/records/' => Providers\Prod\Record::class, + '/prod/records/edit' => Providers\Prod\Edit::class, + '/prod/records/movecollection' => Providers\Prod\MoveCollection::class, + '/prod/records/property' => Providers\Prod\Property::class, + '/prod/share/' => Providers\Prod\Share::class, + '/prod/story' => Providers\Prod\Story::class, + '/prod/tools/' => Providers\Prod\Tools::class, + '/prod/tooltip' => Providers\Prod\Tooltip::class, + '/prod/TOU/' => Providers\Prod\TOU::class, + '/prod/upload/' => Providers\Prod\Upload::class, + '/prod/WorkZone' => Providers\Prod\WorkZone::class, + '/prod/' => Providers\Prod\Root::class, + '/report/activity' => Providers\Report\Activity::class, + '/report/informations' => Providers\Report\Information::class, + '/report/' => Providers\Report\Root::class, + '/session/' => Providers\Root\Session::class, + '/setup' => Providers\Setup::class, + '/thesaurus' => Providers\Thesaurus\Thesaurus::class, + '/user/notifications/' => Providers\User\Notifications::class, + '/user/preferences/' => Providers\User\Preferences::class, + '/xmlhttp' => Providers\Thesaurus\Xmlhttp::class, + '/' => Providers\Root\Root::class, + ]; + + /** + * @var string[] + */ + private $controllerProviders = []; + + /** + * @param string $prefix + * @param string $providerClass + * @throws \InvalidArgumentException + */ + public function registerProvider($prefix, $providerClass) + { + Assertion::classExists($providerClass); + + $this->controllerProviders[$prefix] = $providerClass; + } + + public function registerProviders(array $providers) + { + foreach ($providers as $prefix => $providerClass) { + $this->registerProvider($prefix, $providerClass); + } + } + + /** + * @param Application $app + */ + public function bindRoutes(Application $app) + { + // @todo Move me out of here ! + // Controllers with routes referenced by api + $this->controllerProviders[$app['controller.media_accessor.route_prefix']] = Providers\MediaAccessor::class; + + foreach ($this->controllerProviders as $prefix => $class) { + $app->mount($prefix, new $class); + } + } + + /** + * @param Application $app + * @param $routeParameter + */ + public function bindPluginRoutes(Application $app, $routeParameter) + { + foreach ($app[$routeParameter] as $providerDefinition) { + $prefix = ''; + $providerKey = $providerDefinition; + + if (is_array($providerDefinition)) { + list($prefix, $providerKey) = $providerDefinition; + } + + if (! $this->isValidProviderDefinition($app, $prefix, $providerKey)) { + continue; + } + + $prefix = '/' . ltrim($prefix, '/'); + $provider = $app[$providerKey]; + + if (!$provider instanceof ControllerProviderInterface) { + continue; + } + + $app->mount($prefix, $provider); + } + } + + private function isValidProviderDefinition(Application $app, $prefix, $provider) + { + if (!is_string($prefix) || !is_string($provider)) { + return false; + } + + if (!isset($app[$provider])) { + return false; + } + + return true; + } +} diff --git a/lib/Alchemy/Phrasea/Authentication/RegistrationService.php b/lib/Alchemy/Phrasea/Authentication/RegistrationService.php index 5c0c11d107..0e4d4fcb81 100644 --- a/lib/Alchemy/Phrasea/Authentication/RegistrationService.php +++ b/lib/Alchemy/Phrasea/Authentication/RegistrationService.php @@ -333,7 +333,7 @@ class RegistrationService return; } - $collection = \collection::get_from_base_id($this->app, $baseId); + $collection = \collection::getByBaseId($this->app, $baseId); $registrationManipulator->createRegistration($user, $collection); $successfulRegistrations[$baseId] = $collection; }); diff --git a/lib/Alchemy/Phrasea/Border/Checker/AbstractChecker.php b/lib/Alchemy/Phrasea/Border/Checker/AbstractChecker.php index cb395d6de0..8d6fe00e46 100644 --- a/lib/Alchemy/Phrasea/Border/Checker/AbstractChecker.php +++ b/lib/Alchemy/Phrasea/Border/Checker/AbstractChecker.php @@ -19,8 +19,19 @@ use Alchemy\Phrasea\Border\File; */ abstract class AbstractChecker implements CheckerInterface { + /** + * @var Application + */ protected $app; + + /** + * @var \databox[] + */ protected $databoxes = []; + + /** + * @var \collection[] + */ protected $collections = []; public function __construct(Application $app) @@ -32,8 +43,8 @@ abstract class AbstractChecker implements CheckerInterface * Restrict the checker to a set of databoxes. * Warning, you can not restrict on both databoxes and collections * - * @param databox|array $databoxes A databox or an array of databoxes - * @return Boolean + * @param \databox[] $databoxes A databox or an array of databoxes + * @return bool * * @throws \LogicException If already restricted to collections * @throws \InvalidArgumentException In case invalid databoxes are provided @@ -60,8 +71,8 @@ abstract class AbstractChecker implements CheckerInterface * Restrict the checker to a set of collections. * Warning, you can not restrict on both databoxes and collections * - * @param collection|array $collections - * @return Boolean + * @param \collection[] $collections + * @return bool * * @throws \LogicException If already restricted to databoxes * @throws \InvalidArgumentException In case invalid collections are provided @@ -93,6 +104,10 @@ abstract class AbstractChecker implements CheckerInterface */ public function isApplicable(File $file) { + if (empty($this->databoxes) && empty($this->collections)) { + return true; + } + if (null === $file->getCollection()) { return true; } diff --git a/lib/Alchemy/Phrasea/Border/File.php b/lib/Alchemy/Phrasea/Border/File.php index 58c4d3a54b..74df31a92a 100644 --- a/lib/Alchemy/Phrasea/Border/File.php +++ b/lib/Alchemy/Phrasea/Border/File.php @@ -134,14 +134,12 @@ class File $metadatas->add(new Metadata(TagFactory::getFromRDFTagname($tagname), $value)); } - /** - * PHPExiftool throws exception on some files not supported - */ try { - $this->app['exiftool.writer']->reset(); - $this->app['exiftool.writer']->write($this->getFile()->getRealPath(), $metadatas); + $writer = $this->app['exiftool.writer']; + $writer->reset(); + $writer->write($this->getFile()->getRealPath(), $metadatas); } catch (PHPExiftoolException $e) { - + // PHPExiftool throws exception on some files not supported } } diff --git a/lib/Alchemy/Phrasea/Border/Manager.php b/lib/Alchemy/Phrasea/Border/Manager.php index b0c74650b8..d0b7a4060c 100644 --- a/lib/Alchemy/Phrasea/Border/Manager.php +++ b/lib/Alchemy/Phrasea/Border/Manager.php @@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\Border; +use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Border\Checker\CheckerInterface; use Alchemy\Phrasea\Border\Attribute\AttributeInterface; use Alchemy\Phrasea\Exception\RuntimeException; @@ -27,12 +28,9 @@ use Alchemy\Phrasea\Model\Entities\LazaretAttribute; use Alchemy\Phrasea\Model\Entities\LazaretCheck; use Alchemy\Phrasea\Model\Entities\LazaretFile; use Alchemy\Phrasea\Model\Entities\LazaretSession; -use MediaAlchemyst\Exception\ExceptionInterface as MediaAlchemystException; -use MediaAlchemyst\Specification\Image as ImageSpec; use PHPExiftool\Driver\Metadata\Metadata; use PHPExiftool\Driver\Value\Mono as MonoValue; use PHPExiftool\Driver\Value\Multi; -use Silex\Application; use Symfony\Component\Filesystem\Exception\IOException; /** @@ -44,6 +42,9 @@ use Symfony\Component\Filesystem\Exception\IOException; */ class Manager { + /** + * @var CheckerInterface[] + */ protected $checkers = []; protected $app; protected $filesystem; @@ -159,7 +160,9 @@ class Manager } foreach ($this->checkers as $checker) { - $visa->addResponse($checker->check($this->app['orm.em'], $file)); + if ($checker->isApplicable($file)) { + $visa->addResponse($checker->check($this->app['orm.em'], $file)); + } } return $visa; @@ -242,42 +245,11 @@ class Manager return array_values($this->checkers); } - /** - * Find an available Lazaret filename and creates the empty file. - * - * @param string $filename The desired filename - * @param string $suffix A suffix to the filename - * @return string The available filename to use - */ - protected function bookLazaretPathfile($filename, $suffix = '') - { - $output = $this->app['tmp.path'].'/lazaret/lzrt_' . substr($filename, 0, 3) . '_' . $suffix . '.' . pathinfo($filename, PATHINFO_EXTENSION); - $infos = pathinfo($output); - $n = 0; - - $this->app['filesystem']->mkdir($this->app['tmp.lazaret.path']); - - while (true) { - $output = sprintf('%s/%s-%d%s', $infos['dirname'], $infos['filename'], ++ $n, (isset($infos['extension']) ? '.' . $infos['extension'] : '')); - - try { - if ( ! $this->app['filesystem']->exists($output)) { - $this->app['filesystem']->touch($output); - break; - } - } catch (IOException $e) { - - } - } - - return realpath($output); - } - /** * Adds a record to Phraseanet * * @param File $file The package file - * @return \record_adater + * @return \record_adapter */ protected function createRecord(File $file, $nosubdef=false) { @@ -384,21 +356,8 @@ class Manager ) ); - $lazaretPathname = $this->bookLazaretPathfile($file->getOriginalName()); - $lazaretPathnameThumb = $this->bookLazaretPathfile($file->getOriginalName(), 'thumb'); - - $this->app['filesystem']->copy($file->getFile()->getRealPath(), $lazaretPathname, true); - - $spec = new ImageSpec(); - - $spec->setResizeMode(ImageSpec::RESIZE_MODE_INBOUND_FIXEDRATIO); - $spec->setDimensions(375, 275); - - try { - $this->app['media-alchemyst']->turnInto($file->getFile()->getPathname(), $lazaretPathnameThumb, $spec); - } catch (MediaAlchemystException $e) { - - } + $lazaretFilesystemService = $this->app['phraseanet.lazaret_filesystem']; + $persistedLazaret = $lazaretFilesystemService->writeLazaret($file); $lazaretFile = new LazaretFile(); $lazaretFile->setBaseId($file->getCollection()->get_base_id()); @@ -408,8 +367,8 @@ class Manager $lazaretFile->setForced($forced); - $lazaretFile->setFilename(pathinfo($lazaretPathname, PATHINFO_BASENAME)); - $lazaretFile->setThumbFileName(pathinfo($lazaretPathnameThumb, PATHINFO_BASENAME)); + $lazaretFile->setFilename($persistedLazaret->getFilename()); + $lazaretFile->setThumbFileName($persistedLazaret->getThumbnailFilename()); $lazaretFile->setSession($session); diff --git a/lib/Alchemy/Phrasea/Collection/Collection.php b/lib/Alchemy/Phrasea/Collection/Collection.php new file mode 100644 index 0000000000..adce9e1705 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Collection.php @@ -0,0 +1,250 @@ +databoxId = (int) $databoxId; + $this->collectionId = (int) $collectionId; + $this->name = (string) $name; + $this->preferences = << + + 0 + + +EOT; + $this->logo = ''; + $this->labels = array( + 'en' => '', + 'fr' => '', + 'de' => '', + 'nl' => '' + ); + $this->publicWatermark = ''; + } + + /** + * @return int + */ + public function getDataboxId() + { + return $this->databoxId; + } + + /** + * @return int + */ + public function getCollectionId() + { + return $this->collectionId; + } + + /** + * @param $collectionId + */ + public function setCollectionId($collectionId) + { + if ($this->collectionId > 0) { + throw new LogicException('Cannot change the ID of an existing collection.'); + } + + $this->collectionId = (int) $collectionId; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $name = trim(strip_tags($name)); + + if ($name === '') { + throw new \InvalidArgumentException(); + } + + $this->name = $name; + } + + /** + * @return \string[] + */ + public function getLabels() + { + return $this->labels; + } + + /** + * @param \string[] $labels + */ + public function setLabels($labels) + { + $this->labels = $labels; + } + + /** + * @param $lang + * @param bool $substitute + * @return string + */ + public function getLabel($lang, $substitute = true) + { + if (!array_key_exists($lang, $this->labels)) { + throw new \InvalidArgumentException(sprintf('Code %s is not defined', $lang)); + } + + if ($substitute) { + return isset($this->labels[$lang]) ? $this->labels[$lang] : $this->name; + } else { + return $this->labels[$lang]; + } + } + + /** + * @param $lang + * @param $label + */ + public function setLabel($lang, $label) + { + if (!array_key_exists($lang, $this->labels)) { + throw new \InvalidArgumentException(sprintf("Language '%s' is not defined.", $lang)); + } + + $this->labels[$lang] = $label; + } + + /** + * @return \int[]|string|null + */ + public function getLogo() + { + return $this->logo; + } + + /** + * @param \int[]|string $logo + */ + public function setLogo($logo) + { + $this->logo = $logo; + } + + /** + * @return \DateTimeInterface + */ + public function getLogoUpdatedAt() + { + return $this->logoUpdatedAt; + } + + /** + * @return string + */ + public function getPublicWatermark() + { + return $this->publicWatermark; + } + + /** + * @param string $publicWatermark + */ + public function setPublicWatermark($publicWatermark) + { + if (! in_array($publicWatermark, ['none', 'wm', 'stamp'])) { + return; + } + + $this->publicWatermark = $publicWatermark; + } + + /** + * @return string + */ + public function getPreferences() + { + return $this->preferences; + } + + /** + * @param string $preferences + */ + public function setPreferences($preferences) + { + $this->preferences = $preferences; + } + + /** + * @return CollectionReference + */ + public function getCollectionReference() + { + return $this->collectionReference; + } + + /** + * @param CollectionReference $collectionReference + */ + public function setCollectionReference($collectionReference) + { + $this->collectionReference = $collectionReference; + } +} diff --git a/lib/Alchemy/Phrasea/Collection/CollectionFactory.php b/lib/Alchemy/Phrasea/Collection/CollectionFactory.php new file mode 100644 index 0000000000..34c99c19b0 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/CollectionFactory.php @@ -0,0 +1,72 @@ +app = $application; + } + + /** + * @param int $databoxId + * @param CollectionReference $reference + * @param array $row + * @return \collection + */ + public function create($databoxId, CollectionReference $reference, array $row) + { + if ($databoxId != $reference->getDataboxId()) { + throw new \InvalidArgumentException('Reference does not belong to given databoxId.'); + } + + $collection = new Collection($databoxId, $row['coll_id'], $row['asciiname']); + + $collection->setLabel('en', $row['label_en']); + $collection->setLabel('fr', $row['label_fr']); + $collection->setLabel('de', $row['label_de']); + $collection->setLabel('nl', $row['label_nl']); + $collection->setLogo($row['logo']); + $collection->setPreferences($row['prefs']); + $collection->setPublicWatermark($row['pub_wm']); + + return new \collection($this->app, $collection, $reference, $row); + } + + /** + * @param int $databoxId + * @param CollectionReference[] $collectionReferences + * @param array $rows + * @return array + */ + public function createMany($databoxId, $collectionReferences, array $rows) + { + Assertion::allIsInstanceOf($collectionReferences, CollectionReference::class); + + $collections = []; + $indexedReferences = []; + + foreach ($collectionReferences as $reference) { + $indexedReferences[$reference->getCollectionId()] = $reference; + } + + foreach ($rows as $row) { + $collections[$row['coll_id']] = $this->create($databoxId, $indexedReferences[$row['coll_id']], $row); + } + + return $collections; + } +} diff --git a/lib/Alchemy/Phrasea/Collection/CollectionRepository.php b/lib/Alchemy/Phrasea/Collection/CollectionRepository.php new file mode 100644 index 0000000000..865ece521d --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/CollectionRepository.php @@ -0,0 +1,30 @@ +application = $app; + $this->repositoryFactory = $collectionRepositoryFactory; + $this->referenceRepository = $referenceRepository; + } + + /** + * @param $databoxId + * @return CollectionRepository + */ + public function getRepositoryByDatabox($databoxId) + { + if (!isset($this->repositories[$databoxId])) { + $this->repositories[$databoxId] = $this->repositoryFactory->createRepositoryForDatabox($databoxId); + } + + return $this->repositories[$databoxId]; + } + + /** + * @param int $baseId + * @return CollectionRepository + * @throws \OutOfBoundsException if no repository was found for the given baseId. + */ + public function getRepositoryByBase($baseId) + { + if ($this->baseIdMap === null) { + $this->loadBaseIdMap(); + } + + if (isset($this->baseIdMap[$baseId])) { + return $this->getRepositoryByDatabox($this->baseIdMap[$baseId]); + } + + throw new \OutOfBoundsException('No repository available for given base [baseId: ' . $baseId . ' ].'); + } + + public function purgeRegistry() + { + $this->baseIdMap = null; + + $appBox = $this->application->getApplicationBox(); + + \phrasea::reset_baseDatas($appBox); + \phrasea::reset_sbasDatas($appBox); + } + + private function loadBaseIdMap() + { + $references = $this->referenceRepository->findAll(); + + $this->baseIdMap = []; + + foreach ($references as $reference) { + $this->baseIdMap[$reference->getBaseId()] = $reference->getDataboxId(); + } + } +} diff --git a/lib/Alchemy/Phrasea/Collection/CollectionService.php b/lib/Alchemy/Phrasea/Collection/CollectionService.php new file mode 100644 index 0000000000..0cb33fc4fe --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/CollectionService.php @@ -0,0 +1,266 @@ +app = $application; + $this->connection = $connection; + $this->connectionProvider = $connectionProvider; + } + + /** + * @param Collection $collection + * @return int|null + * @throws \Doctrine\DBAL\DBALException + */ + public function getRecordCount(Collection $collection) + { + $connection = $this->connectionProvider->getConnection($collection->getDataboxId()); + + $sql = "SELECT COALESCE(COUNT(record_id), 0) AS recordCount FROM record WHERE coll_id = :coll_id"; + $stmt = $connection->prepare($sql); + $stmt->execute([':coll_id' => $collection->getCollectionId()]); + $rowbas = $stmt->fetch(\PDO::FETCH_ASSOC); + $stmt->closeCursor(); + + $amount = $rowbas ? (int) $rowbas["recordCount"] : null; + + return $amount; + } + + /** + * @param Collection $collection + * @return array + */ + public function getRecordDetails(Collection $collection) + { + $sql = "SELECT record.coll_id,name,COALESCE(asciiname, CONCAT('_',record.coll_id)) AS asciiname, + SUM(1) AS n, SUM(size) AS size + FROM record NATURAL JOIN subdef + INNER JOIN coll ON record.coll_id=coll.coll_id AND coll.coll_id = :coll_id + GROUP BY record.coll_id, subdef.name"; + + $connection = $this->connectionProvider->getConnection($collection->getDataboxId()); + + $stmt = $connection->prepare($sql); + $stmt->execute([':coll_id' => $collection->getCollectionId()]); + $rs = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $stmt->closeCursor(); + + $ret = []; + foreach ($rs as $row) { + $ret[] = [ + "coll_id" => (int) $row["coll_id"], + "name" => $row["name"], + "amount" => (int) $row["n"], + "size" => (int) $row["size"]]; + } + + return $ret; + } + + /** + * @param Collection $collection + * @return $this + * @throws \Doctrine\DBAL\DBALException + */ + public function resetWatermark(Collection $collection) + { + $sql = 'SELECT path, file FROM record r INNER JOIN subdef s USING(record_id) + WHERE r.coll_id = :coll_id AND r.type="image" AND s.name="preview"'; + + $connection = $this->connectionProvider->getConnection($collection->getDataboxId()); + + $stmt = $connection->prepare($sql); + $stmt->execute([':coll_id' => $collection->getCollectionId()]); + + while ($row2 = $stmt->fetch(\PDO::FETCH_ASSOC)) { + @unlink(\p4string::addEndSlash($row2['path']) . 'watermark_' . $row2['file']); + } + $stmt->closeCursor(); + + return $this; + } + + /** + * @param Collection $collection + * @param int|null $record_id + * @return $this + * @throws \Doctrine\DBAL\DBALException + */ + public function resetStamp(Collection $collection, $record_id = null) + { + $sql = 'SELECT path, file FROM record r INNER JOIN subdef s USING(record_id) + WHERE r.coll_id = :coll_id + AND r.type="image" AND s.name IN ("preview", "document")'; + + + $params = [':coll_id' => $collection->getCollectionId()]; + + if ($record_id) { + $sql .= ' AND record_id = :record_id'; + $params[':record_id'] = $record_id; + } + + $connection = $this->connectionProvider->getConnection($collection->getDataboxId()); + + $stmt = $connection->prepare($sql); + $stmt->execute($params); + + while ($row2 = $stmt->fetch(\PDO::FETCH_ASSOC)) { + @unlink(\p4string::addEndSlash($row2['path']) . 'stamp_' . $row2['file']); + } + $stmt->closeCursor(); + + return $this; + } + + /** + * @param \databox $databox + * @param Collection $collection + * @param CollectionReference $reference + * @throws \Doctrine\DBAL\DBALException + */ + public function delete(\databox $databox, Collection $collection, CollectionReference $reference) + { + while ($this->getRecordCount($collection) > 0) { + $this->emptyCollection($databox, $collection); + } + + $connection = $this->connectionProvider->getConnection($collection->getDataboxId()); + + $sql = "DELETE FROM coll WHERE coll_id = :coll_id"; + $stmt = $connection->prepare($sql); + $stmt->execute([':coll_id' => $collection->getCollectionId()]); + $stmt->closeCursor(); + + $sql = "DELETE FROM bas WHERE base_id = :base_id"; + $stmt = $this->connection->prepare($sql); + $stmt->execute([':base_id' => $reference->getBaseId()]); + $stmt->closeCursor(); + + $sql = "DELETE FROM basusr WHERE base_id = :base_id"; + $stmt = $this->connection->prepare($sql); + $stmt->execute([':base_id' => $reference->getBaseId()]); + $stmt->closeCursor(); + + return; + } + + /** + * @param \databox $databox + * @param Collection $collection + * @param int $pass_quantity + * @return $this + * @throws \Doctrine\DBAL\DBALException + */ + public function emptyCollection(\databox $databox, Collection $collection, $pass_quantity = 100) + { + $pass_quantity = (int) $pass_quantity > 200 ? 200 : (int) $pass_quantity; + $pass_quantity = (int) $pass_quantity < 10 ? 10 : (int) $pass_quantity; + + $sql = "SELECT record_id FROM record WHERE coll_id = :coll_id + ORDER BY record_id DESC LIMIT 0, " . $pass_quantity; + + $connection = $this->connectionProvider->getConnection($collection->getDataboxId()); + + $stmt = $connection->prepare($sql); + $stmt->execute([':coll_id' => $collection->getCollectionId()]); + $rs = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $stmt->closeCursor(); + + foreach ($rs as $row) { + $record = $databox->get_record($row['record_id']); + $record->delete(); + unset($record); + } + + return $this; + } + + /** + * @param CollectionReference $reference + * @return $this + * @throws \Doctrine\DBAL\DBALException + */ + public function unmountCollection(CollectionReference $reference) + { + $params = [':base_id' => $reference->getBaseId()]; + + $query = $this->app['phraseanet.user-query']; + $total = $query->on_base_ids([$reference->getBaseId()]) + ->include_phantoms(false) + ->include_special_users(true) + ->include_invite(true) + ->include_templates(true)->get_total(); + $n = 0; + + while ($n < $total) { + $results = $query->limit($n, 50)->execute()->get_results(); + + foreach ($results as $user) { + $this->app->getAclForUser($user)->delete_data_from_cache(\ACL::CACHE_RIGHTS_SBAS); + $this->app->getAclForUser($user)->delete_data_from_cache(\ACL::CACHE_RIGHTS_BAS); + } + + $n+=50; + } + + $sql = "DELETE FROM basusr WHERE base_id = :base_id"; + $stmt = $this->connection->prepare($sql); + $stmt->execute($params); + $stmt->closeCursor(); + + $sql = "DELETE FROM bas WHERE base_id = :base_id"; + $stmt = $this->connection->prepare($sql); + $stmt->execute($params); + $stmt->closeCursor(); + } + + /** + * @param CollectionReference $reference + * @param User $user + */ + public function grantAdminRights(CollectionReference $reference, User $user) + { + $rights = [ + "canputinalbum" => "1", + "candwnldhd" => "1", + "nowatermark" => "1", + "candwnldpreview" => "1", + "cancmd" => "1", + "canadmin" => "1", + "actif" => "1", + "canreport" => "1", + "canpush" => "1", + "basusr_infousr" => "", + "canaddrecord" => "1", + "canmodifrecord" => "1", + "candeleterecord" => "1", + "chgstatus" => "1", + "imgtools" => "1", + "manage" => "1", + "modify_struct" => "1" + ]; + + $this->app->getAclForUser($user)->update_rights_to_base($reference->getBaseId(), $rights); + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Factory/ArrayCachedCollectionRepositoryFactory.php b/lib/Alchemy/Phrasea/Collection/Factory/ArrayCachedCollectionRepositoryFactory.php new file mode 100644 index 0000000000..bec14d37e9 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Factory/ArrayCachedCollectionRepositoryFactory.php @@ -0,0 +1,34 @@ +collectionRepositoryFactory = $collectionRepositoryFactory; + } + + /** + * @param int $databoxId + * @return CollectionRepository + */ + public function createRepositoryForDatabox($databoxId) + { + $repository = $this->collectionRepositoryFactory->createRepositoryForDatabox($databoxId); + + return new ArrayCacheCollectionRepository($repository); + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Factory/CachedCollectionRepositoryFactory.php b/lib/Alchemy/Phrasea/Collection/Factory/CachedCollectionRepositoryFactory.php new file mode 100644 index 0000000000..601bd9671b --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Factory/CachedCollectionRepositoryFactory.php @@ -0,0 +1,66 @@ +application = $application; + $this->collectionRepositoryFactory = $collectionRepositoryFactory; + $this->cache = $cache; + $this->baseCacheKey = (string)$baseCacheKey; + } + + /** + * @param int $databoxId + * @return CollectionRepository + */ + public function createRepositoryForDatabox($databoxId) + { + $repository = $this->collectionRepositoryFactory->createRepositoryForDatabox($databoxId); + + return new CachedCollectionRepository( + $this->application, + $repository, + $this->cache, + $this->baseCacheKey . '.' . $databoxId + ); + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Factory/DbalCollectionRepositoryFactory.php b/lib/Alchemy/Phrasea/Collection/Factory/DbalCollectionRepositoryFactory.php new file mode 100644 index 0000000000..415c401851 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Factory/DbalCollectionRepositoryFactory.php @@ -0,0 +1,60 @@ +databoxConnectionProvider = $connectionProvider; + $this->collectionFactory = $collectionFactory; + $this->collectionReferenceRepository = $referenceRepository; + } + + /** + * @param int $databoxId + * @return CollectionRepository + */ + public function createRepositoryForDatabox($databoxId) + { + $databoxConnection = $this->databoxConnectionProvider->getConnection($databoxId); + + return new DbalCollectionRepository( + $databoxId, + $databoxConnection, + $this->collectionReferenceRepository, + $this->collectionFactory + ); + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php new file mode 100644 index 0000000000..1ec30411c6 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php @@ -0,0 +1,110 @@ +repository = $referenceRepository; + } + + /** + * @return CollectionReference[] + */ + public function findAll() + { + if ($this->referenceCache === null) { + $this->referenceCache = $this->repository->findAll(); + } + + return $this->referenceCache; + } + + /** + * @param int $databoxId + * @return CollectionReference[] + */ + public function findAllByDatabox($databoxId) + { + $references = $this->findAll(); + $found = array(); + + foreach ($references as $reference) { + if ($reference->getDataboxId() == $databoxId) { + $found[$reference->getBaseId()] = $reference; + } + } + + return $found; + } + + /** + * @param int $baseId + * @return CollectionReference|null + */ + public function find($baseId) + { + $references = $this->findAll(); + + if (isset($references[$baseId])) { + return $references[$baseId]; + } + + return null; + } + + /** + * @param int $databoxId + * @param int $collectionId + * @return CollectionReference|null + */ + public function findByCollectionId($databoxId, $collectionId) + { + $references = $this->findAll(); + + foreach ($references as $reference) { + if ($reference->getDataboxId() == $databoxId && $reference->getCollectionId() == $collectionId) { + return $reference; + } + } + + return null; + } + + /** + * @param CollectionReference $reference + * @return void + */ + public function save(CollectionReference $reference) + { + $this->repository->save($reference); + + if ($this->referenceCache !== null) { + $this->referenceCache[$reference->getBaseId()] = $reference; + } + } + + /** + * @param CollectionReference $reference + * @return void + */ + public function delete(CollectionReference $reference) + { + $this->repository->delete($reference); + + if ($this->referenceCache !== null) { + unset($this->referenceCache[$reference->getBaseId()]); + } + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReference.php b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReference.php new file mode 100644 index 0000000000..5f0f74b3c8 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReference.php @@ -0,0 +1,156 @@ +baseId = (int) $baseId; + $this->databoxId = (int) $databoxId; + $this->collectionId = (int) $collectionId; + $this->displayIndex = (int) $displayIndex; + $this->isActive = (bool) $isActive; + $this->alias = (string) $alias; + } + + /** + * @return int + */ + public function getDataboxId() + { + return $this->databoxId; + } + + /** + * @return int + */ + public function getBaseId() + { + return $this->baseId; + } + + /** + * @param int $baseId + */ + public function setBaseId($baseId) + { + if ($this->baseId > 0) { + throw new \LogicException('Cannot change the baseId of an existing collection reference.'); + } + + $this->baseId = $baseId; + } + + /** + * @return int + */ + public function getCollectionId() + { + return $this->collectionId; + } + + /** + * @return int + */ + public function getDisplayIndex() + { + return $this->displayIndex; + } + + /** + * @param int $index + * @return $this + */ + public function setDisplayIndex($index) + { + $this->displayIndex = (int) $index; + + return $this; + } + + /** + * @return boolean + */ + public function isActive() + { + return $this->isActive; + } + + /** + * @return $this + */ + public function disable() + { + $this->isActive = false; + + return $this; + } + + /** + * @return $this + */ + public function enable() + { + $this->isActive = true; + + return $this; + } + + /** + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * @param string $alias + * @return $this + */ + public function setAlias($alias) + { + $this->alias = (string) $alias; + + return $this; + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php new file mode 100644 index 0000000000..48dbfc94f5 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php @@ -0,0 +1,42 @@ + 'baseId', + 'sbas_id' => 'databoxId', + 'server_coll_id' => 'collectionId', + 'ord' => 'displayIndex', + 'active' => 'isActive', + 'aliases' => 'alias' + ]; + + private static $selectQuery = 'SELECT base_id AS baseId, sbas_id AS databoxId, server_coll_id AS collectionId, + ord AS displayIndex, active AS isActive, aliases AS alias + FROM bas'; + + private static $insertQuery = 'INSERT INTO bas (sbas_id, server_coll_id, ord, active, aliases) + VALUES (:databoxId, :collectionId, + (SELECT COALESCE(MAX(b.ord), 0) + 1 AS ord FROM bas b WHERE b.sbas_id = :databoxId), + :isActive, :alias)'; + + private static $updateQuery = 'UPDATE bas SET ord = :displayIndex, active = :isActive, aliases = :alias + WHERE base_id = :baseId'; + + private static $deleteQuery = 'DELETE FROM bas WHERE base_id = :baseId'; + + /** + * @var Connection + */ + private $connection; + + /** + * @param Connection $connection + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * @return CollectionReference[] + */ + public function findAll() + { + return $this->createManyReferences($this->connection->fetchAll(self::$selectQuery)); + } + + /** + * @param int $databoxId + * @return CollectionReference[] + */ + public function findAllByDatabox($databoxId) + { + $query = self::$selectQuery . ' WHERE sbas_id = :databoxId'; + $rows = $this->connection->fetchAll($query, [ ':databoxId' => $databoxId ]); + + return $this->createManyReferences($rows); + } + + /** + * @param int $baseId + * @return CollectionReference|null + */ + public function find($baseId) + { + $query = self::$selectQuery . ' WHERE base_id = :baseId'; + $row = $this->connection->fetchAssoc($query, [ ':baseId' => $baseId ]); + + if ($row !== false) { + return $this->createReference($row); + } + + return null; + } + + /** + * @param int $databoxId + * @param int $collectionId + * @return CollectionReference|null + */ + public function findByCollectionId($databoxId, $collectionId) + { + $query = self::$selectQuery . ' WHERE sbas_id = :databoxId AND server_coll_id = :collectionId'; + $row = $this->connection->fetchAssoc($query, [ ':databoxId' => $databoxId, ':collectionId' => $collectionId ]); + + if ($row !== false) { + return $this->createReference($row); + } + + return null; + } + + public function save(CollectionReference $collectionReference) + { + $query = self::$insertQuery; + $isInsert = true; + + $parameters = [ + 'isActive' => $collectionReference->isActive(), + 'alias' => $collectionReference->getAlias() + ]; + + if ($collectionReference->getBaseId() > 0) { + $query = self::$updateQuery; + $isInsert = false; + + $parameters['baseId'] = $collectionReference->getBaseId(); + $parameters['displayIndex'] = $collectionReference->getDisplayIndex(); + } + else { + $parameters['databoxId'] = $collectionReference->getDataboxId(); + $parameters['collectionId'] = $collectionReference->getCollectionId(); + } + + $this->connection->executeQuery($query, $parameters); + + if ($isInsert) { + $collectionReference->setBaseId($this->connection->lastInsertId()); + } + } + + /** + * @param CollectionReference $collectionReference + * @throws \Doctrine\DBAL\DBALException + */ + public function delete(CollectionReference $collectionReference) + { + $parameters = [ + 'baseId' => $collectionReference->getBaseId() + ]; + + $this->connection->executeQuery(self::$deleteQuery, $parameters); + } + + /** + * @param array $row + * @return CollectionReference + */ + private function createReference(array $row) + { + return new CollectionReference( + $row['baseId'], + $row['databoxId'], + $row['collectionId'], + $row['displayIndex'], + $row['isActive'], + $row['alias'] + ); + } + + /** + * @param $rows + * @return array + */ + private function createManyReferences($rows) + { + $references = []; + + foreach ($rows as $row) { + $references[$row['baseId']] = $this->createReference($row); + } + + return $references; + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Repository/ArrayCacheCollectionRepository.php b/lib/Alchemy/Phrasea/Collection/Repository/ArrayCacheCollectionRepository.php new file mode 100644 index 0000000000..3a5bc69951 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Repository/ArrayCacheCollectionRepository.php @@ -0,0 +1,69 @@ +collectionRepository = $collectionRepository; + } + + /** + * @return \collection[] + */ + public function findAll() + { + if ($this->collectionCache === null) { + $this->collectionCache = $this->collectionRepository->findAll(); + } + + return $this->collectionCache; + } + + /** + * @param int $collectionId + * @return \collection|null + */ + public function find($collectionId) + { + $collections = $this->findAll(); + + if (isset($collections[$collectionId])) { + return $collections[$collectionId]; + } + + return null; + } + + public function save(Collection $collection) + { + $this->collectionRepository->save($collection); + + if ($this->collectionCache !== null) { + $this->collectionCache = null; + } + } + + public function delete(Collection $collection) + { + $this->collectionRepository->delete($collection); + + if (isset($this->collectionCache[$collection->getCollectionId()])) { + unset($this->collectionCache[$collection->getCollectionId()]); + } + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Repository/CachedCollectionRepository.php b/lib/Alchemy/Phrasea/Collection/Repository/CachedCollectionRepository.php new file mode 100644 index 0000000000..71e68aed02 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Repository/CachedCollectionRepository.php @@ -0,0 +1,122 @@ +app = $application; + $this->repository = $repository; + $this->cache = $cache; + $this->cacheKey = $cacheKey; + } + + /** + * @return \collection[] + */ + public function findAll() + { + $cacheKey = $this->getCacheKey(); + /** @var \collection[] $collections */ + $collections = $this->cache->fetch($cacheKey); + + if ($collections === false) { + $collections = $this->repository->findAll(); + $this->putInCache($cacheKey, $collections); + } else { + foreach ($collections as $collection) { + $collection->hydrate($this->app); + } + } + + return $collections; + } + + /** + * @param int $collectionId + * @return \collection|null + */ + public function find($collectionId) + { + $collections = $this->findAll(); + + if (isset($collections[$collectionId])) { + return $collections[$collectionId]; + } + + return null; + } + + public function save(Collection $collection) + { + $this->repository->save($collection); + + $cacheKey = $this->getCacheKey(); + + $this->cache->delete($cacheKey); + } + + public function delete(Collection $collection) + { + $this->repository->delete($collection); + + $cacheKey = $this->getCacheKey(); + + $this->cache->delete($cacheKey); + } + + private function putInCache($key, $value) + { + $this->cache->save($key, $value); + } + + /** + * @return string + */ + private function getCacheKey() + { + $cacheKey = 'collections:' . hash('sha256', $this->cacheKey); + + return $cacheKey; + } +} diff --git a/lib/Alchemy/Phrasea/Collection/Repository/DbalCollectionRepository.php b/lib/Alchemy/Phrasea/Collection/Repository/DbalCollectionRepository.php new file mode 100644 index 0000000000..24736a7adc --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Repository/DbalCollectionRepository.php @@ -0,0 +1,172 @@ +databoxId = (int) $databoxId; + $this->databoxConnection = $databoxConnection; + $this->referenceRepository = $referenceRepository; + $this->collectionFactory = $collectionFactory; + } + + /** + * @return \collection[] + */ + public function findAll() + { + $references = $this->referenceRepository->findAllByDatabox($this->databoxId); + + if (empty($references)) { + return []; + } + + $parameters = []; + + foreach ($references as $reference) { + $parameters[] = $reference->getCollectionId(); + } + + $query = self::$selectQuery . ' WHERE coll_id IN (:collectionIds)'; + $parameters = [ 'collectionIds' => $parameters ]; + $parameterTypes = [ 'collectionIds' => Connection::PARAM_INT_ARRAY ]; + + $rows = $this->databoxConnection->fetchAll($query, $parameters, $parameterTypes); + + return $this->collectionFactory->createMany($this->databoxId, $references, $rows); + } + + /** + * @param int $baseId + * @return \collection|null + */ + public function find($baseId) + { + $reference = $this->referenceRepository->find($baseId); + + if ($reference === null) { + return null; + } + + $query = self::$selectQuery . ' WHERE coll_id = :collectionId'; + $row = $this->databoxConnection->fetchAssoc($query, [ ':collectionId' => $reference->getCollectionId() ]); + + if ($row !== false) { + return $this->collectionFactory->create($this->databoxId, $reference, $row); + } + + return null; + } + + /** + * @param int $databoxId + * @param int $collectionId + * @return \collection|null + */ + public function findByCollectionId($databoxId, $collectionId) + { + $reference = $this->referenceRepository->findByCollectionId($databoxId, $collectionId); + + if ($reference === null) { + return null; + } + + $query = self::$selectQuery . ' WHERE coll_id = :collectionId'; + $row = $this->databoxConnection->fetchAssoc($query, [ ':collectionId' => $reference->getCollectionId() ]); + + if ($row !== false) { + return $this->collectionFactory->create($this->databoxId, $reference, $row); + } + + return null; + } + + public function save(Collection $collection) + { + $isInsert = true; + $query = self::$insertQuery; + $parameters = array( + 'name' => $collection->getName(), + 'preferences' => $collection->getPreferences(), + 'logo' => $collection->getLogo() + ); + + if ($collection->getCollectionId() > 0) { + $parameters['collectionId'] = $collection->getCollectionId(); + $parameters['labelEn'] = $collection->getLabel('en', false); + $parameters['labelFr'] = $collection->getLabel('fr', false); + $parameters['labelDe'] = $collection->getLabel('de', false); + $parameters['labelNl'] = $collection->getLabel('nl', false); + $parameters['logoTimestamp'] = $collection->getLogoUpdatedAt(); + $parameters['publicWatermark'] = $collection->getPublicWatermark(); + + $query = self::$updateQuery; + $isInsert = false; + } + + $this->databoxConnection->executeQuery($query, $parameters); + + if ($isInsert) { + $collection->setCollectionId($this->databoxConnection->lastInsertId()); + } + } + + public function delete(Collection $collection) + { + $parameters = [ + 'collectionId' => $collection->getCollectionId() + ]; + + $this->databoxConnection->executeQuery(self::$deleteQuery, $parameters); + } +} diff --git a/lib/Alchemy/Phrasea/Command/Developer/IniReset.php b/lib/Alchemy/Phrasea/Command/Developer/IniReset.php index 75edd32fa5..9cf58faec9 100644 --- a/lib/Alchemy/Phrasea/Command/Developer/IniReset.php +++ b/lib/Alchemy/Phrasea/Command/Developer/IniReset.php @@ -1,5 +1,4 @@ getHelperSet()->get('dialog'); $dbName = $dialog->ask( $output, - _('Please enter the databox name to reset or create') + $this->container['translator']->trans('Please enter the databox name to reset or create') ); } } else if ($input->getOption('db-name')) { @@ -75,7 +72,7 @@ class IniReset extends Command } $continue = 'y'; - if (count($dbs['dbs']) > 1 && in_array($dbName, array_map(function($db) { return $db->get_dbname();}, $dbs['dbs']))) { + if (count($dbs['dbs']) > 1 && in_array($dbName, array_map(function(\base $db) { return $db->get_dbname();}, $dbs['dbs']))) { if ($interactive) { do { $continue = mb_strtolower($dialog->ask($output, '' .$dbName.' database is going to be truncated, do you want to continue ? (Y/n)', 'Y')); @@ -87,7 +84,7 @@ class IniReset extends Command return; } - $unmountedDbs = $dbToMount = array_diff(array_map(function($db) { return $db->get_dbname();}, $dbs['dbs']), array($dbName)); + $unmountedDbs = $dbToMount = array_diff(array_map(function(\base $db) { return $db->get_dbname();}, $dbs['dbs']), array($dbName)); if (count($unmountedDbs) > 1 && $interactive) { array_unshift($unmountedDbs, 'all'); diff --git a/lib/Alchemy/Phrasea/Command/Developer/Uninstaller.php b/lib/Alchemy/Phrasea/Command/Developer/Uninstaller.php index 43c5aa9517..a5c3c91659 100644 --- a/lib/Alchemy/Phrasea/Command/Developer/Uninstaller.php +++ b/lib/Alchemy/Phrasea/Command/Developer/Uninstaller.php @@ -31,60 +31,45 @@ class Uninstaller extends Command protected function doExecute(InputInterface $input, OutputInterface $output) { $root = $this->container['root.path']; + $path = $this->container['cache.path']; - foreach ([ - $root.'/config/configuration.yml', - $root.'/config/services.yml', - $root.'/config/connexions.yml', - $root.'/config/config.yml', - $root.'/config/config.inc', - $root.'/config/connexion.inc', - $root.'/config/_GV.php', - $root.'/config/_GV.php.old', - $root.'/config/configuration-compiled.php', - ] as $file) { - if ($this->container['filesystem']->exists($file)) { - unlink($file); - } - } - - foreach ([ + $paths = [ + $root . '/config/configuration.yml', + $root . '/config/services.yml', + $root . '/config/connexions.yml', + $root . '/config/config.yml', + $root . '/config/config.inc', + $root . '/config/connexion.inc', + $root . '/config/_GV.php', + $root . '/config/_GV.php.old', + $root . '/config/configuration-compiled.php', $this->container['tmp.download.path'], $this->container['tmp.lazaret.path'], $this->container['tmp.caption.path'], - $this->container['tmp.path'].'/sessions', - $this->container['tmp.path'].'/locks', - ] as $resource) { - if (is_dir($resource)) { - $finder = new Finder(); - foreach ($finder->files()->in($resource) as $file) { - $this->container['filesystem']->remove($file); - } - } elseif (is_file($resource)) { - $this->container['filesystem']->remove($resource); + $this->container['tmp.path'] . '/sessions', + $this->container['tmp.path'] . '/locks', + $path . '/cache_registry.php', + $path . '/cache_registry.yml', + $path . '/serializer', + $path . '/doctrine', + $path . '/twig', + $path . '/translations', + $path . '/minify', + $path . '/profiler', + ]; + + $files = $directories = []; + + foreach ($paths as $path) { + if (is_dir($path)) { + $directories[] = $path; + } elseif (is_file($path)) { + $files[] = $path; } } - $path = $this->container['cache.path']; - foreach ([ - $path.'/cache_registry.php', - $path.'/cache_registry.yml', - $path.'/serializer', - $path.'/doctrine', - $path.'/twig', - $path.'/translations', - $path.'/minify', - $path.'/profiler', - ] as $resource) { - if (is_dir($resource)) { - $finder = new Finder(); - foreach ($finder->files()->in($resource) as $file) { - $this->container['filesystem']->remove($file); - } - } elseif (is_file($resource)) { - $this->container['filesystem']->remove($resource); - } - } + $this->container['filesystem']->remove($files); + $this->container['filesystem']->remove(Finder::create()->in($directories)); return 0; } diff --git a/lib/Alchemy/Phrasea/Command/RecordAdd.php b/lib/Alchemy/Phrasea/Command/RecordAdd.php index e42a79ee77..e40514ac4c 100644 --- a/lib/Alchemy/Phrasea/Command/RecordAdd.php +++ b/lib/Alchemy/Phrasea/Command/RecordAdd.php @@ -43,7 +43,7 @@ class RecordAdd extends Command protected function doExecute(InputInterface $input, OutputInterface $output) { try { - $collection = \collection::get_from_base_id($this->container, $input->getArgument('base_id')); + $collection = \collection::getByBaseId($this->container, $input->getArgument('base_id')); } catch (\Exception $e) { throw new \InvalidArgumentException(sprintf('Collection %s is invalid', $input->getArgument('base_id'))); } diff --git a/lib/Alchemy/Phrasea/Command/Setup/H264MappingGenerator.php b/lib/Alchemy/Phrasea/Command/Setup/H264MappingGenerator.php index 6626f27cd9..3b4bd93ab2 100644 --- a/lib/Alchemy/Phrasea/Command/Setup/H264MappingGenerator.php +++ b/lib/Alchemy/Phrasea/Command/Setup/H264MappingGenerator.php @@ -12,6 +12,7 @@ namespace Alchemy\Phrasea\Command\Setup; use Alchemy\Phrasea\Command\Command; +use Alchemy\Phrasea\Databox\DataboxPathExtractor; use Alchemy\Phrasea\Http\H264PseudoStreaming\H264Factory; use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Symfony\Component\Console\Input\InputArgument; @@ -36,7 +37,8 @@ class H264MappingGenerator extends Command */ protected function doExecute(InputInterface $input, OutputInterface $output) { - $paths = $this->extractPath($this->container->getApplicationBox()); + $extractor = new DataboxPathExtractor($this->container->getApplicationBox()); + $paths = $extractor->extractPaths(); foreach ($paths as $path) { $this->container['filesystem']->mkdir($path); } @@ -95,22 +97,4 @@ class H264MappingGenerator extends Command return ['mount-point' => 'mp4-videos-'.$n, 'directory' => $path, 'passphrase' => $this->container['random.low']->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS)]; } - - private function extractPath(\appbox $appbox) - { - $paths = []; - - foreach ($appbox->get_databoxes() as $databox) { - foreach ($databox->get_subdef_structure() as $group => $subdefs) { - if ('video' !== $group) { - continue; - } - foreach ($subdefs as $subdef) { - $paths[] = $subdef->get_path(); - } - } - } - - return array_filter(array_unique($paths)); - } } diff --git a/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingGenerator.php b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingGenerator.php index 8b0a618932..fd56a6765b 100644 --- a/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingGenerator.php +++ b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingGenerator.php @@ -12,6 +12,7 @@ namespace Alchemy\Phrasea\Command\Setup; use Alchemy\Phrasea\Command\Command; +use Alchemy\Phrasea\Databox\DataboxPathExtractor; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -35,7 +36,8 @@ class XSendFileMappingGenerator extends Command */ protected function doExecute(InputInterface $input, OutputInterface $output) { - $paths = $this->extractPath($this->container->getApplicationBox()); + $extractor = new DataboxPathExtractor($this->container->getApplicationBox()); + $paths = $extractor->extractPaths(); foreach ($paths as $path) { $this->container['filesystem']->mkdir($path); } @@ -84,20 +86,4 @@ class XSendFileMappingGenerator extends Command return ['mount-point' => 'protected_dir_'.$n, 'directory' => $path]; } - - private function extractPath(\appbox $appbox) - { - $paths = []; - - foreach ($appbox->get_databoxes() as $databox) { - $paths[] = (string) $databox->get_sxml_structure()->path; - foreach ($databox->get_subdef_structure() as $group => $subdefs) { - foreach ($subdefs as $subdef) { - $paths[] = $subdef->get_path(); - } - } - } - - return array_filter(array_unique($paths)); - } } diff --git a/lib/Alchemy/Phrasea/Command/Upgrade/Step31.php b/lib/Alchemy/Phrasea/Command/Upgrade/Step31.php index 32ea0f775e..a7f42db231 100644 --- a/lib/Alchemy/Phrasea/Command/Upgrade/Step31.php +++ b/lib/Alchemy/Phrasea/Command/Upgrade/Step31.php @@ -125,7 +125,7 @@ class Step31 implements DatasUpgraderInterface $uuid = Uuid::uuid4(); try { $media = $this->app->getMediaFromUri($pathfile); - $collection = \collection::get_from_coll_id($this->$app, $databox, (int) $record['coll_id']); + $collection = \collection::getByCollectionId($this->$app, $databox, (int) $record['coll_id']); $file = new File($this->app, $media, $collection); $uuid = $file->getUUID(true, true); diff --git a/lib/Alchemy/Phrasea/Controller/AbstractDelivery.php b/lib/Alchemy/Phrasea/Controller/AbstractDelivery.php index dcb0f07dd2..e550c21912 100644 --- a/lib/Alchemy/Phrasea/Controller/AbstractDelivery.php +++ b/lib/Alchemy/Phrasea/Controller/AbstractDelivery.php @@ -16,7 +16,6 @@ use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware; use Alchemy\Phrasea\Application\Helper\DelivererAware; use Alchemy\Phrasea\Http\DeliverDataInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; abstract class AbstractDelivery { @@ -33,26 +32,20 @@ abstract class AbstractDelivery public function deliverContent(Request $request, \record_adapter $record, $subdef, $watermark, $stamp) { - $file = $record->get_subdef($subdef); - $pathOut = $file->get_pathfile(); + $mediaSubdefinition = $record->get_subdef($subdef); - if ($watermark === true && $file->get_type() === \media_subdef::TYPE_IMAGE) { - $pathOut = \recordutils_image::watermark($this->app, $file); - } elseif ($stamp === true && $file->get_type() === \media_subdef::TYPE_IMAGE) { - $pathOut = \recordutils_image::stamp($this->app, $file); - } + $pathOut = $this->tamperProofSubDefinition($mediaSubdefinition, $watermark, $stamp); $disposition = $request->query->get('download') ? DeliverDataInterface::DISPOSITION_ATTACHMENT : DeliverDataInterface::DISPOSITION_INLINE; - /** @var Response $response */ - $response = $this->deliverFile($pathOut, $file->get_file(), $disposition, $file->get_mime()); + $response = $this->deliverFile($pathOut, $mediaSubdefinition->get_file(), $disposition, $mediaSubdefinition->get_mime()); if (in_array($subdef, array('document', 'preview'))) { $response->setPrivate(); $this->logView($record, $request); } elseif ($subdef !== 'thumbnail') { try { - if ($file->getDataboxSubdef()->get_class() != \databox_subdef::CLASS_THUMBNAIL) { + if ($mediaSubdefinition->getDataboxSubdef()->get_class() != \databox_subdef::CLASS_THUMBNAIL) { $response->setPrivate(); $this->logView($record, $request); } @@ -81,4 +74,23 @@ abstract class AbstractDelivery // Ignore exception } } + + /** + * @param \media_subdef $mediaSubdefinition + * @param bool $watermark + * @param bool $stamp + * @return string + */ + private function tamperProofSubDefinition(\media_subdef $mediaSubdefinition, $watermark, $stamp) + { + $pathOut = $mediaSubdefinition->getRealPath(); + + if ($watermark === true && $mediaSubdefinition->get_type() === \media_subdef::TYPE_IMAGE) { + $pathOut = \recordutils_image::watermark($this->app, $mediaSubdefinition); + } elseif ($stamp === true && $mediaSubdefinition->get_type() === \media_subdef::TYPE_IMAGE) { + $pathOut = \recordutils_image::stamp($this->app, $mediaSubdefinition); + } + + return $pathOut; + } } diff --git a/lib/Alchemy/Phrasea/Controller/Admin/CollectionController.php b/lib/Alchemy/Phrasea/Controller/Admin/CollectionController.php index a8254560fb..ac082bb3ca 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/CollectionController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/CollectionController.php @@ -33,7 +33,7 @@ class CollectionController extends Controller */ public function getCollection(Request $request, $bas_id) { - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); $admins = []; @@ -144,7 +144,7 @@ class CollectionController extends Controller $success = false; $msg = $this->app->trans('An error occurred'); - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { if ($collection->get_record_amount() <= 500) { $collection->empty_collection(500); @@ -184,7 +184,7 @@ class CollectionController extends Controller { $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $this->app->getApplicationBox()->write_collection_pic( @@ -224,7 +224,7 @@ class CollectionController extends Controller { $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $this->app->getApplicationBox()->write_collection_pic( @@ -264,7 +264,7 @@ class CollectionController extends Controller { $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $collection->update_logo(null); @@ -323,7 +323,7 @@ class CollectionController extends Controller ]); } - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $this->app->getApplicationBox()->write_collection_pic( @@ -378,7 +378,7 @@ class CollectionController extends Controller ]); } - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $this->app->getApplicationBox()->write_collection_pic( @@ -432,7 +432,7 @@ class CollectionController extends Controller ]); } - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $this->app->getApplicationBox()->write_collection_pic( @@ -468,13 +468,13 @@ class CollectionController extends Controller $success = false; $msg = $this->app->trans('An error occured'); - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { if ($collection->get_record_amount() > 0) { $msg = $this->app->trans('Empty the collection before removing'); } else { - $collection->unmount_collection($this->app); + $collection->unmount(); $collection->delete(); $success = true; $msg = $this->app->trans('Successful removal'); @@ -522,10 +522,10 @@ class CollectionController extends Controller { $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { - $collection->unmount_collection($this->app); + $collection->unmount(); $success = true; } catch (\Exception $e) { @@ -562,7 +562,7 @@ class CollectionController extends Controller $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $collection->set_name($name); @@ -594,7 +594,7 @@ class CollectionController extends Controller $this->app->abort(400, $this->app->trans('Invalid labels parameter')); } - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); $success = true; try { @@ -638,7 +638,7 @@ class CollectionController extends Controller $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $collection->set_public_presentation($watermark); @@ -671,7 +671,7 @@ class CollectionController extends Controller { $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $collection->enable($this->app->getApplicationBox()); @@ -704,7 +704,7 @@ class CollectionController extends Controller { $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); try { $collection->disable($this->app->getApplicationBox()); @@ -736,7 +736,7 @@ class CollectionController extends Controller { /** @var \databox $databox */ $databox = $this->app->findDataboxById(\phrasea::sbasFromBas($this->app, $bas_id)); - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); $structFields = $suggestedValues = $basePrefs = []; /** @var \databox_field $meta */ @@ -806,7 +806,7 @@ class CollectionController extends Controller { $success = false; - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); $prefs = $request->request->get('str'); try { @@ -843,7 +843,7 @@ class CollectionController extends Controller */ public function getDetails($bas_id) { - $collection = \collection::get_from_base_id($this->app, $bas_id); + $collection = \collection::getByBaseId($this->app, $bas_id); $out = ['total' => ['totobj' => 0, 'totsiz' => 0, 'mega' => '0', 'giga' => '0'], 'result' => []]; diff --git a/lib/Alchemy/Phrasea/Controller/Admin/DashboardController.php b/lib/Alchemy/Phrasea/Controller/Admin/DashboardController.php index 82abeb8543..86dfb14d98 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/DashboardController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/DashboardController.php @@ -124,6 +124,10 @@ class DashboardController extends Controller public function addAdmins(Request $request) { $admins = $request->request->get('admins', []); + + // Remove empty values + $admins = array_filter($admins); + if (!is_array($admins) || count($admins) === 0) { $this->app->abort(400, '"admins" parameter must contains at least one value.'); } @@ -134,6 +138,15 @@ class DashboardController extends Controller } $userRepository = $this->getUserRepository(); + + $demotedAdmins = []; + + foreach ($userRepository->findAdmins() as $admin) { + if (!in_array($admin->getId(), $admins)) { + $demotedAdmins[$admin->getId()] = $admin; + } + } + $userRepository->findBy(['id' => $admins]); $admins = array_map(function ($usrId) use ($userRepository) { if (null === $user = $userRepository->find($usrId)) { @@ -145,7 +158,10 @@ class DashboardController extends Controller /** @var UserManipulator $userManipulator */ $userManipulator = $this->app['manipulator.user']; + + $userManipulator->demote($demotedAdmins); $userManipulator->promote($admins); + /** @var ACLManipulator $aclManipulator */ $aclManipulator = $this->app['manipulator.acl']; $aclManipulator->resetAdminRights($admins); diff --git a/lib/Alchemy/Phrasea/Controller/Admin/DataboxController.php b/lib/Alchemy/Phrasea/Controller/Admin/DataboxController.php index 0cc7529682..f1e8e5e6a0 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/DataboxController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/DataboxController.php @@ -633,7 +633,7 @@ class DataboxController extends Controller { try { foreach ($request->request->get('order', []) as $data) { - $collection = \collection::get_from_base_id($this->app, $data['id']); + $collection = \collection::getByBaseId($this->app, $data['id']); $collection->set_ord($data['offset']); } $success = true; @@ -712,7 +712,7 @@ class DataboxController extends Controller } catch (\Exception $e) { return $this->app->redirectPath('admin_database_submit_collection', [ 'databox_id' => $databox_id, - 'error' => 'error', + 'error' => $e->getMessage(), ]); } } diff --git a/lib/Alchemy/Phrasea/Controller/Admin/FeedController.php b/lib/Alchemy/Phrasea/Controller/Admin/FeedController.php index 59ce41c97b..4180cb1d84 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/FeedController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/FeedController.php @@ -56,7 +56,7 @@ class FeedController extends Controller if ($request->request->get('public') == '1') { $feed->setIsPublic(true); } elseif ($request->request->get('base_id')) { - $feed->setCollection(\collection::get_from_base_id($this->app, $request->request->get('base_id'))); + $feed->setCollection(\collection::getByBaseId($this->app, $request->request->get('base_id'))); } $publisher->setFeed($feed); @@ -106,7 +106,7 @@ class FeedController extends Controller } try { - $collection = \collection::get_from_base_id($this->app, $request->request->get('base_id')); + $collection = \collection::getByBaseId($this->app, $request->request->get('base_id')); } catch (\Exception $e) { $collection = null; } diff --git a/lib/Alchemy/Phrasea/Controller/Admin/RootController.php b/lib/Alchemy/Phrasea/Controller/Admin/RootController.php index 6433b02c4e..901d2efc95 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/RootController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/RootController.php @@ -32,8 +32,7 @@ class RootController extends Controller 'module' => 'admin', 'events' => $this->app['events-manager'], 'module_name' => 'Admin', - 'notice' => $request->query->get("notice"), - 'tree' => $this->render('admin/tree.html.twig', $params), + 'notice' => $request->query->get("notice") ], $params)); } diff --git a/lib/Alchemy/Phrasea/Controller/Admin/UserController.php b/lib/Alchemy/Phrasea/Controller/Admin/UserController.php index eabcbc9a6f..07d8bc8674 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/UserController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/UserController.php @@ -455,7 +455,7 @@ class UserController extends Controller $registrationRepository->getUserRegistrations( $user, array_map(function ($baseId) { - return \collection::get_from_base_id($this->app, $baseId); + return \collection::getByBaseId($this->app, $baseId); }, $bases) ) as $registration) { $registrationManipulator->rejectRegistration($registration); @@ -473,7 +473,7 @@ class UserController extends Controller foreach ($registrationRepository->getUserRegistrations( $user, array_map(function ($baseId) { - return \collection::get_from_base_id($this->app, $baseId); + return \collection::getByBaseId($this->app, $baseId); }, $bases) ) as $registration) { $done[$usr][$registration->getBaseId()] = true; @@ -503,7 +503,7 @@ class UserController extends Controller ]; foreach ($bases as $bas => $isok) { - $collection = \collection::get_from_base_id($this->app, $bas); + $collection = \collection::getByBaseId($this->app, $bas); $label = $collection->get_label($this->app['locale']); if ($isok) { diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php b/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php index 778e0c1df5..3eaabe8f3b 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php @@ -837,7 +837,7 @@ class V1Controller extends Controller return $this->getBadRequestAction($request, 'Missing base_id parameter'); } - $collection = \collection::get_from_base_id($this->app, $request->get('base_id')); + $collection = \collection::getByBaseId($this->app, $request->get('base_id')); if (!$this->getAclForUser()->has_right_on_base($request->get('base_id'), 'canaddrecord')) { return Result::createError($request, 403, sprintf( @@ -935,7 +935,7 @@ class V1Controller extends Controller $media = $this->app->getMediaFromUri($file->getPathname()); $record = $this->findDataboxById($request->get('databox_id'))->get_record($request->get('record_id')); $base_id = $record->getBaseId(); - $collection = \collection::get_from_base_id($this->app, $base_id); + $collection = \collection::getByBaseId($this->app, $base_id); if (!$this->getAclForUser()->has_right_on_base($base_id, 'canaddrecord')) { return Result::createError($request, 403, sprintf( 'You do not have access to collection %s', $collection->get_label($this->app['locale.I18n']) @@ -943,7 +943,7 @@ class V1Controller extends Controller } $adapt = ($request->get('adapt')===null || !(\p4field::isno($request->get('adapt')))); $ret['adapt'] = $adapt; - $record->substitute_subdef($request->get('name'), $media, $this->app, $adapt); + $this->getSubdefSubstituer()->substitute($record, $request->get('name'), $media, $adapt); foreach ($record->get_embedable_medias() as $name => $media) { if ($name == $request->get('name') && null !== ($subdef = $this->listEmbeddableMedia($request, $record, $media))) { @@ -1611,7 +1611,7 @@ class V1Controller extends Controller $record = $databox->get_record($record_id); try { - $collection = \collection::get_from_base_id($this->app, $request->get('base_id')); + $collection = \collection::getByBaseId($this->app, $request->get('base_id')); $record->move_to_collection($collection, $this->getApplicationBox()); return Result::create($request, ["record" => $this->listRecord($request, $record)])->createResponse(); @@ -2067,7 +2067,7 @@ class V1Controller extends Controller */ protected function createStory($data) { - $collection = \collection::get_from_base_id($this->app, $data->{'base_id'}); + $collection = \collection::getByBaseId($this->app, $data->{'base_id'}); if (!$this->getAclForUser()->has_right_on_base($collection->get_base_id(), 'canaddrecord')) { $this->app->abort(403, sprintf('You can not create a story on this collection %s', $collection->get_base_id())); @@ -2235,8 +2235,8 @@ class V1Controller extends Controller if (!in_array($name, array('thumbnail', 'preview'))) { continue; } - $media = $this->app->getMediaFromUri($value->get_pathfile()); - $story->substitute_subdef($name, $media, $this->app); + $media = $this->app->getMediaFromUri($value->getRealPath()); + $this->getSubdefSubstituer()->substitute($story, $name, $media); $this->getDataboxLogger($story->getDatabox())->log( $story, \Session_Logger::EVENT_SUBSTITUTE, @@ -2304,7 +2304,7 @@ class V1Controller extends Controller $ret = [ 'success' => true ]; } catch (AccountException $exception) { - $ret = [ 'success' => false, 'message' => _($exception->getMessage()) ]; + $ret = [ 'success' => false, 'message' => $this->app->trans($exception->getMessage()) ]; } return Result::create($request, $ret)->createResponse(); @@ -2327,7 +2327,7 @@ class V1Controller extends Controller $service->updatePassword($command, null); $ret = ['success' => true]; } catch (AccountException $exception) { - $ret = [ 'success' => false, 'message' => _($exception->getMessage()) ]; + $ret = [ 'success' => false, 'message' => $this->app->trans($exception->getMessage()) ]; } } else { $ret = [ 'success' => false, 'message' => (string) $form->getErrorsAsString() ]; @@ -2567,4 +2567,12 @@ class V1Controller extends Controller { return $this->app['phraseanet.SE.logger']; } + + /** + * @return \Alchemy\Phrasea\Media\SubdefSubstituer + */ + private function getSubdefSubstituer() + { + return $this->app['subdef.substituer']; + } } diff --git a/lib/Alchemy/Phrasea/Controller/MediaAccessorController.php b/lib/Alchemy/Phrasea/Controller/MediaAccessorController.php index 43f105685c..6ca16d9491 100644 --- a/lib/Alchemy/Phrasea/Controller/MediaAccessorController.php +++ b/lib/Alchemy/Phrasea/Controller/MediaAccessorController.php @@ -52,18 +52,7 @@ class MediaAccessorController extends Controller public function showAction(Request $request, $token) { - try { - $token = JWT::decode($token, $this->keyStorage, $this->allowedAlgorithms); - } catch (\UnexpectedValueException $exception) { - throw new NotFoundHttpException('Resource not found', $exception); - } catch (\Exception $exception) { - throw new BadRequestHttpException('Invalid token', $exception); - } - - if (! isset($token->sdef) || !is_array($token->sdef) || count($token->sdef) !== 3) { - throw new BadRequestHttpException('sdef should be a sub-definition identifier.'); - } - list ($sbas_id, $record_id, $subdef) = $token->sdef; + list($sbas_id, $record_id, $subdef) = $this->validateToken($token); try { $databox = $this->findDataboxById($sbas_id); @@ -93,4 +82,39 @@ class MediaAccessorController extends Controller return $response; } + + /** + * @param string $token + * @return object + */ + public function decodeToken($token) + { + try { + return JWT::decode($token, $this->keyStorage, $this->allowedAlgorithms); + } catch (\UnexpectedValueException $exception) { + throw new NotFoundHttpException('Resource not found', $exception); + } catch (\Exception $exception) { + throw new BadRequestHttpException('Invalid token', $exception); + } + } + + /** + * Validate token and returns triplet containing sbas_id, record_id and subdef. + * + * @param string|object $token + * @return array + */ + public function validateToken($token) + { + if (is_string($token)) { + $token = $this->decodeToken($token); + } + + if (!isset($token->sdef) || !is_array($token->sdef) || count($token->sdef) !== 3) { + throw new BadRequestHttpException('sdef should be a sub-definition identifier.'); + } + list ($sbas_id, $record_id, $subdef) = $token->sdef; + + return array($sbas_id, $record_id, $subdef); + } } diff --git a/lib/Alchemy/Phrasea/Controller/PermalinkController.php b/lib/Alchemy/Phrasea/Controller/PermalinkController.php index 12379c9be7..04aed5ec2c 100644 --- a/lib/Alchemy/Phrasea/Controller/PermalinkController.php +++ b/lib/Alchemy/Phrasea/Controller/PermalinkController.php @@ -11,11 +11,12 @@ namespace Alchemy\Phrasea\Controller; use Alchemy\Embed\Media\Media; +use Alchemy\Embed\Media\MediaInformation; use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Application\Helper\ApplicationBoxAware; use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Authentication\Authenticator; use Alchemy\Phrasea\Model\Repositories\BasketElementRepository; -use Alchemy\Phrasea\Model\Repositories\FeedItemRepository; use Alchemy\Phrasea\Model\Serializer\CaptionSerializer; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -23,20 +24,19 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class PermalinkController extends AbstractDelivery { + use ApplicationBoxAware; + /** @var ACLProvider */ private $acl; - /** @var \appbox */ - private $appbox; /** @var Authenticator */ private $authentication; /** @var Media */ private $mediaService; - public function __construct(Application $app, \appbox $appbox, ACLProvider $acl, Authenticator $authenticator, Media $mediaService) + public function __construct(Application $app, ACLProvider $acl, Authenticator $authenticator, Media $mediaService) { parent::__construct($app); - $this->appbox = $appbox; $this->acl = $acl; $this->authentication = $authenticator; $this->mediaService = $mediaService; @@ -44,9 +44,9 @@ class PermalinkController extends AbstractDelivery public function getOptionsResponse(Request $request, $sbas_id, $record_id) { - $databox = $this->mediaService->getDatabox($sbas_id); + $databox = $this->findDataboxById($sbas_id); $token = $request->query->get('token'); - $record = $this->mediaService->retrieveRecord($databox, $token, $record_id, $request->get('subdef', 'thumbnail')); + $record = $this->retrieveRecord($databox, $token, $record_id, $request->get('subdef', 'thumbnail')); if (null === $record) { throw new NotFoundHttpException("Record not found"); @@ -57,9 +57,9 @@ class PermalinkController extends AbstractDelivery public function deliverCaption(Request $request, $sbas_id, $record_id) { - $databox = $this->mediaService->getDatabox($sbas_id); + $databox = $this->findDataboxById($sbas_id); $token = $request->query->get('token'); - $record = $this->mediaService->retrieveRecord($databox, $token, $record_id, \databox_subdef::CLASS_THUMBNAIL); + $record = $this->retrieveRecord($databox, $token, $record_id, \databox_subdef::CLASS_THUMBNAIL); if (null === $record) { throw new NotFoundHttpException("Caption not found"); @@ -74,7 +74,38 @@ class PermalinkController extends AbstractDelivery return $this->doDeliverPermaview($sbas_id, $record_id, $request->query->get('token'), $subdef); } - public function deliverPermaviewOldWay($sbas_id, $record_id, $token, $subdef) + private function doDeliverPermaview($sbas_id, $record_id, $token, $subdefName) + { + $databox = $this->findDataboxById($sbas_id); + $record = $this->retrieveRecord($databox, $token, $record_id, $subdefName); + $subdef = $record->get_subdef($subdefName); + + $information = $this->mediaService->createMediaInformationFromResourceAndRoute( + $subdef, + 'permalinks_permalink', + [ + 'sbas_id' => $sbas_id, + 'record_id' => $record_id, + 'subdef' => $subdefName, + 'label' => $record->get_title(), + 'token' => $token, + ] + ); + $metaData = $this->mediaService->getMetaData($information); + + return $this->app['twig']->render('overview.html.twig', [ + 'ogMetaData' => $metaData['ogMetaData'], + 'subdef' => $subdef, + 'module_name' => 'overview', + 'module' => 'overview', + 'view' => 'overview', + 'token' => $token, + 'record' => $record, + 'recordUrl' => $information->getUrl(), + ]); + } + + public function deliverPermaviewOldWay(Request $request, $sbas_id, $record_id, $token, $subdef) { return $this->doDeliverPermaview($sbas_id, $record_id, $token, $subdef); } @@ -84,35 +115,10 @@ class PermalinkController extends AbstractDelivery return $this->doDeliverPermalink($request, $sbas_id, $record_id, $request->query->get('token'), $subdef); } - public function deliverPermalinkOldWay(Request $request, $sbas_id, $record_id, $token, $subdef) - { - return $this->doDeliverPermalink($request, $sbas_id, $record_id, $token, $subdef); - } - - private function doDeliverPermaview($sbas_id, $record_id, $token, $subdefName) - { - - $databox = $this->mediaService->getDatabox($sbas_id); - $record = $this->mediaService->retrieveRecord($databox, $token, $record_id, $subdefName); - $metaDatas = $this->mediaService->getMetaDatas($record, $subdefName); - $subdef = $record->get_subdef($subdefName); - - return $this->app['twig']->render('overview.html.twig', [ - 'ogMetaDatas' => $metaDatas['ogMetaDatas'], - 'subdef' => $subdef, - 'module_name' => 'overview', - 'module' => 'overview', - 'view' => 'overview', - 'token' => $token, - 'record' => $record, - ]); - } - private function doDeliverPermalink(Request $request, $sbas_id, $record_id, $token, $subdef) { - $databox = $this->mediaService->getDatabox($sbas_id); - // $record = $this->retrieveRecord($databox, $token, $record_id, $subdef); - $record = $this->mediaService->retrieveRecord($databox, $token, $record_id, $subdef); + $databox = $this->findDataboxById($sbas_id); + $record = $this->retrieveRecord($databox, $token, $record_id, $subdef); $watermark = $stamp = false; if ($this->authentication->isAuthenticated()) { @@ -132,7 +138,7 @@ class PermalinkController extends AbstractDelivery return $this->deliverContentWithCaptionLink($request, $record, $subdef, $watermark, $stamp, $token); } - $collection = \collection::get_from_base_id($this->app, $record->get_base_id()); + $collection = \collection::getByBaseId($this->app, $record->get_base_id()); switch ($collection->get_pub_wm()) { default: case 'none': @@ -170,4 +176,42 @@ class PermalinkController extends AbstractDelivery return $response; } + + public function deliverPermalinkOldWay(Request $request, $sbas_id, $record_id, $token, $subdef) + { + return $this->doDeliverPermalink($request, $sbas_id, $record_id, $token, $subdef); + } + + /** + * @param \databox $databox + * @param string $token + * @param int $record_id + * @param string $subdef + * @return \record_adapter + */ + private function retrieveRecord(\databox $databox, $token, $record_id, $subdef) + { + try { + $record = $databox->get_record($record_id); + $subDefinition = $record->get_subdef($subdef); + $permalink = $subDefinition->get_permalink(); + } catch (\Exception $exception) { + throw new NotFoundHttpException('Wrong token.', $exception); + } + + if (null === $permalink || !$permalink->get_is_activated()) { + throw new NotFoundHttpException('This token has been disabled.'); + } + + $feedItemsRepository = $this->app['repo.feed-items']; + if (in_array($subdef, [\databox_subdef::CLASS_PREVIEW, \databox_subdef::CLASS_THUMBNAIL]) + && $feedItemsRepository->isRecordInPublicFeed($databox->get_sbas_id(), $record_id) + ) { + return $record; + } elseif ($permalink->get_token() == (string)$token) { + return $record; + } + + throw new NotFoundHttpException('Wrong token.'); + } } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/EditController.php b/lib/Alchemy/Phrasea/Controller/Prod/EditController.php index ffbb76b386..d935211685 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/EditController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/EditController.php @@ -297,7 +297,7 @@ class EditController extends Controller continue; } - $media = $this->app->getMediaFromUri($value->get_pathfile()); + $media = $this->app->getMediaFromUri($value->getRealPath()); $this->getSubDefinitionSubstituer()->substitute($reg_record, $name, $media); $this->getDispatcher()->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($reg_record)); $this->getDataboxLogger($reg_record->get_databox())->log( diff --git a/lib/Alchemy/Phrasea/Controller/Prod/MoveCollectionController.php b/lib/Alchemy/Phrasea/Controller/Prod/MoveCollectionController.php index c6daefa300..74c8378e84 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/MoveCollectionController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/MoveCollectionController.php @@ -58,7 +58,7 @@ class MoveCollectionController extends Controller } try { - $collection = \collection::get_from_base_id($this->app, $request->request->get('base_id')); + $collection = \collection::getByBaseId($this->app, $request->request->get('base_id')); } catch (\Exception_Databox_CollectionNotFound $e) { $datas['message'] = $this->app->trans('Invalid target collection'); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/PropertyController.php b/lib/Alchemy/Phrasea/Controller/Prod/PropertyController.php index e4e832521c..f368b24046 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/PropertyController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/PropertyController.php @@ -30,13 +30,14 @@ class PropertyController extends Controller $records = RecordsRequest::fromRequest($this->app, $request, false, ['chgstatus']); - if (count($records->databoxes()) > 1) { + $databoxes = $records->databoxes(); + if (count($databoxes) > 1) { return new Response($this->render('prod/actions/Property/index.html.twig', [ 'records' => $records, ])); } - $databox = current($records->databoxes()); + $databox = reset($databoxes); $statusStructure = $databox->getStatusStructure(); $recordsStatuses = []; diff --git a/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php b/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php index 86b121ef46..839fa0f2dc 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php @@ -156,7 +156,11 @@ class QueryController extends Controller . $this->app->trans('%total% reponses', ['%total%' => ''.$result->getTotal().'']) . ''; $json['infos'] = $infoResult; - $json['navigation'] = $string; + $json['navigationTpl'] = $string; + $json['navigation'] = [ + 'page' => $page, + 'perPage' => $perPage + ]; $prop = null; diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ShareController.php b/lib/Alchemy/Phrasea/Controller/Prod/ShareController.php index 207d64f4d5..85df3351cf 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ShareController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ShareController.php @@ -41,35 +41,13 @@ class ShareController extends Controller $preview = $record->get_preview(); - if ($preview->get_permalink() !== null) { + if (null !== $previewLink = $preview->get_permalink()) { + $permalinkUrl = $previewLink->get_url(); + $permaviewUrl = $previewLink->get_page(); + $previewWidth = $preview->get_width(); + $previewHeight = $preview->get_height(); - - $subdefName = $preview->get_name(); - $subdef = $record->get_subdef($subdefName); - - switch ($record->getType()) { - - case 'flexpaper': - case 'document': - case 'audio': - case 'video': - default: - $token = $preview->get_permalink()->get_token(); - $permalinkUrl = $preview->get_permalink()->get_url(); - $permaviewUrl = $preview->get_permalink()->get_page(); - $previewWidth = $preview->get_width(); - $previewHeight = $preview->get_height(); - break; - } - - - $sbas_id = $record->getDataboxId(); - $embedUrl = $this->app->url('alchemy_embed_view', [ - 'sbas_id' => $sbas_id, - 'record_id' => $record_id, - 'subdefName' => $subdefName, - 'token' => $token, - ]); + $embedUrl = $this->app->url('alchemy_embed_view', ['url' => (string)$permalinkUrl]); $outputVars = [ 'isAvailable' => true, diff --git a/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php b/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php index c74b6ddce4..e8efc59e47 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php @@ -33,7 +33,7 @@ class StoryController extends Controller public function postCreateFormAction(Request $request) { - $collection = \collection::get_from_base_id($this->app, $request->request->get('base_id')); + $collection = \collection::getByBaseId($this->app, $request->request->get('base_id')); if (!$this->getAclForUser()->has_right_on_base($collection->get_base_id(), 'canaddrecord')) { throw new AccessDeniedHttpException('You can not create a story on this collection'); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php index 4632c197f6..a0784a46eb 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php @@ -83,7 +83,7 @@ class ToolsController extends Controller if (!$record->isStory()) { try { $metadata = $this->getExifToolReader() - ->files($record->get_subdef('document')->get_pathfile()) + ->files($record->get_subdef('document')->getRealPath()) ->first()->getMetadatas(); } catch (PHPExiftoolException $e) { // ignore diff --git a/lib/Alchemy/Phrasea/Controller/Prod/UploadController.php b/lib/Alchemy/Phrasea/Controller/Prod/UploadController.php index 9ca3c5f9d9..1c04bfd251 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/UploadController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/UploadController.php @@ -133,7 +133,7 @@ class UploadController extends Controller $this->getFilesystem()->rename($uploadedFilename, $renamedFilename); $media = $this->app->getMediaFromUri($renamedFilename); - $collection = \collection::get_from_base_id($this->app, $base_id); + $collection = \collection::getByBaseId($this->app, $base_id); $lazaretSession = new LazaretSession(); $lazaretSession->setUser($this->getAuthenticatedUser()); diff --git a/lib/Alchemy/Phrasea/Controller/Root/AccountController.php b/lib/Alchemy/Phrasea/Controller/Root/AccountController.php index 7acbc0d5d8..87bcbdc721 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/AccountController.php +++ b/lib/Alchemy/Phrasea/Controller/Root/AccountController.php @@ -316,7 +316,7 @@ class AccountController extends Controller if (0 !== count($registrations)) { foreach ($registrations as $baseId) { $this->getRegistrationManipulator() - ->createRegistration($user, \collection::get_from_base_id($this->app, $baseId)); + ->createRegistration($user, \collection::getByBaseId($this->app, $baseId)); } $this->app->addFlash('success', $this->app->trans('Your registration requests have been taken into account.')); } diff --git a/lib/Alchemy/Phrasea/Controller/Root/SessionController.php b/lib/Alchemy/Phrasea/Controller/Root/SessionController.php index fcd23652bf..ece103e59c 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/SessionController.php +++ b/lib/Alchemy/Phrasea/Controller/Root/SessionController.php @@ -82,7 +82,7 @@ class SessionController extends Controller if (in_array($this->getSession()->get('phraseanet.message'), ['1', null])) { if ($this->app['phraseanet.configuration']['main']['maintenance']) { - $ret['message'] .= _('The application is going down for maintenance, please logout.'); + $ret['message'] .= $this->app->trans('The application is going down for maintenance, please logout.'); } if ($this->getConf()->get(['registry', 'maintenance', 'enabled'], false)) { diff --git a/lib/Alchemy/Phrasea/ControllerProvider/ControllerProviderServiceProvider.php b/lib/Alchemy/Phrasea/ControllerProvider/ControllerProviderServiceProvider.php new file mode 100644 index 0000000000..92e35853ab --- /dev/null +++ b/lib/Alchemy/Phrasea/ControllerProvider/ControllerProviderServiceProvider.php @@ -0,0 +1,106 @@ +loadProviders(); + + foreach ($this->controllerProviders as $class => $values) { + $app->register(new $class, $values); + } + } + + /** + * Bootstraps the application. + * + * This method is called after all services are registered + * and should be used for "dynamic" configuration (whenever + * a service must be requested). + */ + public function boot(Application $app) + { + // Nothing to do here + } + + public function loadProviders() + { + $this->controllerProviders = [ + Admin\Collection::class => [], + Admin\ConnectedUsers::class => [], + Admin\Dashboard::class => [], + Admin\Databox::class => [], + Admin\Databoxes::class => [], + Admin\Feeds::class => [], + Admin\Fields::class => [], + Admin\Plugins::class => [], + Admin\Root::class => [], + Admin\SearchEngine::class => [], + Admin\Setup::class => [], + Admin\Subdefs::class => [], + Admin\TaskManager::class => [], + Admin\Users::class => [], + Client\Root::class => [], + Datafiles::class => [], + Lightbox::class => [], + MediaAccessor::class => [], + Minifier::class => [], + Permalink::class => [], + Prod\BasketProvider::class => [], + Prod\Bridge::class => [], + Prod\DoDownload::class => [], + Prod\Download::class => [], + Prod\Edit::class => [], + Prod\Export::class => [], + Prod\Feed::class => [], + Prod\Language::class => [], + Prod\Lazaret::class => [], + Prod\MoveCollection::class => [], + Prod\Order::class => [], + Prod\Printer::class => [], + Prod\Property::class => [], + Prod\Push::class => [], + Prod\Query::class => [], + Prod\Record::class => [], + Prod\Root::class => [], + Prod\Share::class => [], + Prod\Story::class => [], + Prod\Tools::class => [], + Prod\Tooltip::class => [], + Prod\TOU::class => [], + Prod\Upload::class => [], + Prod\UsrLists::class => [], + Prod\WorkZone::class => [], + Report\Activity::class => [], + Report\Information::class => [], + Report\Root::class => [], + Root\Account::class => [], + Root\Developers::class => [], + Root\Login::class => [], + Root\Root::class => [], + Root\RSSFeeds::class => [], + Root\Session::class => [], + Setup::class => [], + Thesaurus\Thesaurus::class => [], + Thesaurus\Xmlhttp::class => [], + User\Notifications::class => [], + User\Preferences::class => [], + EmbedServiceProvider::class => [], + ]; + } +} diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Permalink.php b/lib/Alchemy/Phrasea/ControllerProvider/Permalink.php index dac57361d1..619c7526c9 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Permalink.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Permalink.php @@ -24,9 +24,15 @@ class Permalink implements ControllerProviderInterface, ServiceProviderInterface public function register(Application $app) { $app['controller.permalink'] = $app->share(function (PhraseaApplication $app) { - return (new PermalinkController($app, $app->getApplicationBox(), $app['acl'], $app->getAuthenticator(), $app['alchemy_embed.service.media'])) + return (new PermalinkController( + $app, + $app['acl'], + $app->getAuthenticator(), + $app['alchemy_embed.service.media'] + )) ->setDataboxLoggerLocator($app['phraseanet.logger']) ->setDelivererLocator(new LazyLocator($app, 'phraseanet.file-serve')) + ->setApplicationBox($app['phraseanet.appbox']) ; }); } diff --git a/lib/Alchemy/Phrasea/Core/Configuration/AccessRestriction.php b/lib/Alchemy/Phrasea/Core/Configuration/AccessRestriction.php index ef76784639..ff5ccd6a8a 100644 --- a/lib/Alchemy/Phrasea/Core/Configuration/AccessRestriction.php +++ b/lib/Alchemy/Phrasea/Core/Configuration/AccessRestriction.php @@ -1,9 +1,8 @@ conf = $conf; + $this->propertyAccess = $propertyAccess; $this->appbox = $appbox; $this->logger = $logger; - $this->cache = $cache; } /** * Returns true if a configuration is set. * - * @return Boolean + * @return bool */ public function isRestricted() { $this->load(); - return $this->cache->fetch('restricted'); + return $this->restricted; } /** @@ -49,7 +74,7 @@ class AccessRestriction * * @param \databox $databox * - * @return Boolean + * @return bool */ public function isDataboxAvailable(\databox $databox) { @@ -57,7 +82,7 @@ class AccessRestriction return true; } - return in_array($databox->get_sbas_id(), $this->cache->fetch('available_databoxes'), true); + return in_array($databox->get_sbas_id(), $this->availableDataboxes, true); } /** @@ -72,7 +97,7 @@ class AccessRestriction return $databoxes; } - $available = array_flip($this->cache->fetch('available_databoxes')); + $available = array_flip($this->availableDataboxes); return array_filter($databoxes, function (\databox $databox) use ($available) { return isset($available[$databox->get_sbas_id()]); @@ -84,7 +109,7 @@ class AccessRestriction * * @param \collection $collection * - * @return Boolean + * @return bool */ public function isCollectionAvailable(\collection $collection) { @@ -92,30 +117,31 @@ class AccessRestriction return true; } - $availableCollections = $this->cache->fetch('available_collections_'.$collection->get_databox()->get_sbas_id()) ?: []; + $availableCollections = isset($this->availableCollections[$collection->get_sbas_id()]) + ? $this->availableCollections[$collection->get_sbas_id()] : []; return in_array($collection->get_base_id(), $availableCollections, true); } private function load() { - if ($this->cache->fetch('loaded')) { + if ($this->loaded) { return; } - $this->cache->save('loaded', true); + $this->loaded = true; $allowedDataboxIds = array_map(function ($dbConf) { return $dbConf['id']; - }, $this->conf->get('databoxes', [])); + }, $this->propertyAccess->get('databoxes', [])); if (count($allowedDataboxIds) === 0) { - $this->cache->save('restricted', false); + $this->restricted = false; return; } - $this->cache->save('restricted', true); + $this->restricted = true; $databoxIds = array_map(function (\databox $databox) { return $databox->get_sbas_id(); }, $this->appbox->get_databoxes()); $errors = array_diff($allowedDataboxIds, $databoxIds); @@ -124,18 +150,15 @@ class AccessRestriction $this->logger->error(sprintf('Misconfiguration for allowed databoxes : ids %s do not exist', implode(', ', $errors))); } - $allowedDataboxIds = array_intersect($allowedDataboxIds, $databoxIds); - $this->cache->save('available_databoxes', $allowedDataboxIds); + $this->availableDataboxes = array_intersect($allowedDataboxIds, $databoxIds); $this->loadCollections(); } private function loadCollections() { - $allowedDataboxIds = $this->cache->fetch('available_databoxes'); - - foreach ($this->conf->get('databoxes') as $databox) { - if (!in_array($databox['id'], $allowedDataboxIds, true)) { + foreach ($this->propertyAccess->get('databoxes') as $databox) { + if (!in_array($databox['id'], $this->availableDataboxes, true)) { continue; } @@ -148,9 +171,7 @@ class AccessRestriction $this->logger->error(sprintf('Misconfiguration for allowed collections : ids %s do not exist', implode(', ', $errors))); } - $collections = array_intersect($collections, $availableBaseIds); - - $this->cache->save('available_collections_'.$databox['id'], $collections); + $this->availableCollections[$databox['id']] = array_intersect($collections, $availableBaseIds); } } } diff --git a/lib/Alchemy/Phrasea/Core/Configuration/DisplaySettingService.php b/lib/Alchemy/Phrasea/Core/Configuration/DisplaySettingService.php index 751945ffed..b82b3501e0 100644 --- a/lib/Alchemy/Phrasea/Core/Configuration/DisplaySettingService.php +++ b/lib/Alchemy/Phrasea/Core/Configuration/DisplaySettingService.php @@ -33,7 +33,7 @@ class DisplaySettingService 'warning_on_delete_story' => 'true', 'client_basket_status' => '1', 'css' => '000000', - 'start_page_query' => 'last', + 'start_page_query' => '', 'start_page' => 'QUERY', 'rollover_thumbnail' => 'caption', 'technical_display' => '1', diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/CacheStatisticsSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/CacheStatisticsSubscriber.php new file mode 100644 index 0000000000..c0f05b6def --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/CacheStatisticsSubscriber.php @@ -0,0 +1,120 @@ +cache = $cache; + $this->cacheType = $cache->getName(); + } + + public function getCacheNamespace() + { + if ($this->cache instanceof TraceableCache) { + return $this->cache->getNamespace(); + } + + return '[ root ]'; + } + + public function getCallSummary() + { + if ($this->cache instanceof TraceableCache) { + return $this->cache->getSummary(); + } + + return [ + 'calls' => 0, + 'hits' => 0, + 'misses' => 0, + 'calls_by_type' => [], + 'calls_by_key' => [] + ]; + } + + public function getCalls() + { + if ($this->cache instanceof TraceableCache) { + return $this->cache->getCalls(); + } + + return []; + } + + public function getTimeSpent() + { + if ($this->cache instanceof TraceableCache) { + return $this->cache->getTotalTime(); + } + + return 0; + } + + public function getCacheType() + { + return $this->cacheType; + } + + public function getInitialStats() + { + return $this->stats; + } + + public function getCurrentStats() + { + return $this->cache->getStats(); + } + + public function onKernelRequest() + { + $this->stats = $this->cache->getStats(); + } + + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ KernelEvents::REQUEST => [ 'onKernelRequest', 2048 ] ]; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/DebuggerSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/DebuggerSubscriber.php index 1a13e6fccf..696546f7dd 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/DebuggerSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/DebuggerSubscriber.php @@ -37,10 +37,6 @@ class DebuggerSubscriber implements EventSubscriberInterface public function checkIp(GetResponseEvent $event) { - if (Application::ENV_DEV !== $this->app->getEnvironment()) { - return; - } - if ($this->app['configuration.store']->isSetup() && $this->app['conf']->has(['debugger', 'allowed-ips'])) { $allowedIps = $this->app['conf']->get(['debugger', 'allowed-ips']); $allowedIps = is_array($allowedIps) ? $allowedIps : [$allowedIps]; diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/RegistrationSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/RegistrationSubscriber.php index 4aa96e0392..6266983a18 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/RegistrationSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/RegistrationSubscriber.php @@ -1,9 +1,8 @@ getLogin()); - $body .= sprintf("%s : %s\n", _('admin::compte-utilisateur nom'), $registeredUser->getFirstName()); - $body .= sprintf("%s : %s\n", _('admin::compte-utilisateur prenom'), $registeredUser->getLastName()); - $body .= sprintf("%s : %s\n", _('admin::compte-utilisateur email'), $registeredUser->getEmail()); + $body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur nom'), $registeredUser->getFirstName()); + $body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur prenom'), $registeredUser->getLastName()); + $body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur email'), $registeredUser->getEmail()); $body .= sprintf("%s/%s\n", $registeredUser->get_job(), $registeredUser->getCompany()); $readyToSend = false; diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/TrustedProxySubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/TrustedProxySubscriber.php index a993d64533..faafd01efb 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/TrustedProxySubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/TrustedProxySubscriber.php @@ -29,7 +29,7 @@ class TrustedProxySubscriber implements EventSubscriberInterface public static function getSubscribedEvents() { return [ - KernelEvents::REQUEST => ['setProxyConf', 0], + KernelEvents::REQUEST => ['setProxyConf', 1024], ]; } diff --git a/lib/Alchemy/Phrasea/Core/MetaProvider/DatabaseMetaProvider.php b/lib/Alchemy/Phrasea/Core/MetaProvider/DatabaseMetaProvider.php new file mode 100644 index 0000000000..84ecea5311 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/MetaProvider/DatabaseMetaProvider.php @@ -0,0 +1,102 @@ +register(new DoctrineServiceProvider()); + $this->setupDBAL($app); + $app->register(new DoctrineOrmServiceProvider()); + $this->setupOrms($app); + $app->register(new ORMServiceProvider()); + } + + private function setupDBAL(PhraseaApplication $app) + { + $app['dbs.config'] = $app->share($app->extend('dbs.config', function ($configs, $app) { + if (! $app->isDebug()) { + return $configs; + } + + foreach($configs->keys() as $service) { + $app['dbal.config.register.loggers']($configs[$service]); + } + + return $configs; + })); + + $app['dbs.event_manager'] = $app->share($app->extend('dbs.event_manager', function ($eventManagers, $app) { + foreach ($eventManagers->keys() as $name) { + /** @var EventManager $eventManager */ + $eventManager = $eventManagers[$name]; + $app['dbal.evm.register.listeners']($eventManager); + + $eventManager->addEventListener(Events::postConnect, $app); + } + + return $eventManagers; + })); + } + + private function setupOrms(PhraseaApplication $app) + { + // Override "orm.cache.configurer" service provided for benefiting + // of "phraseanet.cache-service" + $app['orm.cache.configurer'] = $app->protect(function($name, Configuration $config, $options) use ($app) { + /** @var Manager $service */ + $service = $app['phraseanet.cache-service']; + + $config->setMetadataCacheImpl( + $service->factory('ORM_metadata', $app['orm.cache.driver'], $app['orm.cache.options']) + ); + $config->setQueryCacheImpl( + $service->factory('ORM_query', $app['orm.cache.driver'], $app['orm.cache.options']) + ); + $config->setResultCacheImpl( + $service->factory('ORM_result', $app['orm.cache.driver'], $app['orm.cache.options']) + ); + $config->setHydrationCacheImpl( + $service->factory('ORM_hydration', $app['orm.cache.driver'], $app['orm.cache.options']) + ); + }); + + $app['orm.proxies_dir'] = $app['root.path'].'/resources/proxies'; + $app['orm.auto_generate_proxies'] = $app['debug']; + $app['orm.proxies_namespace'] = 'Alchemy\Phrasea\Model\Proxies'; + + $app['orm.ems'] = $app->share($app->extend('orm.ems', function (\Pimple $ems, $app) { + GedmoExtension::registerAnnotations(); + + foreach ($ems->keys() as $key) { + $app['orm.annotation.register']($key); + $connection = $ems[$key]->getConnection(); + + $app['connection.pool.manager']->add($connection); + + $types = $app['orm.ems.options'][$key]['types']; + $app['dbal.type.register']($connection, $types); + } + + return $ems; + })); + } + + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Core/MetaProvider/HttpStackMetaProvider.php b/lib/Alchemy/Phrasea/Core/MetaProvider/HttpStackMetaProvider.php new file mode 100644 index 0000000000..b3a308fc18 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/MetaProvider/HttpStackMetaProvider.php @@ -0,0 +1,107 @@ +register(new HttpFragmentServiceProvider()); + $app->register(new UrlGeneratorServiceProvider()); + + $this->setupRequestContext($app); + + $app->register(new SessionHandlerServiceProvider()); + $app->register(new SessionServiceProvider(), [ + 'session.test' => $app->getEnvironment() === PhraseaApplication::ENV_TEST, + 'session.storage.options' => ['cookie_lifetime' => 0] + ]); + + $app['session.storage.test'] = $app->share(function () { + return new MockArraySessionStorage(); + }); + + $app['session.storage.handler'] = $app->share(function (Application $app) { + if (!$app['phraseanet.configuration-tester']->isInstalled()) { + return new NullSessionHandler(); + } + return $app['session.storage.handler.factory']->create($app['conf']); + }); + + $app->register(new ControllerProviderServiceProvider()); + + $this->registerCors($app); + } + + public function setupRequestContext(Application $app) + { + $app['request_context'] = $app->share($app->extend('request_context', function (RequestContext $context, Application $app) { + if ($app['configuration.store']->isSetup()) { + $data = parse_url($app['conf']->get('servername')); + + if (isset($data['scheme'])) { + $context->setScheme($data['scheme']); + } + if (isset($data['host'])) { + $context->setHost($data['host']); + } + } + + return $context; + })); + } + + public function registerCors(Application $app) + { + $app->register(new ContentNegotiationServiceProvider()); + $app->register(new CorsServiceProvider(), [ + 'alchemy_cors.debug' => $app['debug'], + 'alchemy_cors.cache_path' => function (Application $app) { + return rtrim($app['cache.path'], '/\\') . '/alchemy_cors.cache.php'; + }, + ]); + + $app['phraseanet.api_cors.options_provider'] = function (Application $app) { + $paths = []; + + if (isset($app['phraseanet.configuration']['api_cors'])) { + $config = $app['phraseanet.configuration']['api_cors']; + + if (isset($config['enabled']) && $config['enabled']) { + unset($config['enabled']); + + $paths['/api/v\d+/'] = $config; + } + } + + return new DefaultProvider($paths, []); + }; + + $app['alchemy_cors.options_providers'][] = 'phraseanet.api_cors.options_provider'; + } + + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Core/MetaProvider/MediaUtilitiesMetaServiceProvider.php b/lib/Alchemy/Phrasea/Core/MetaProvider/MediaUtilitiesMetaServiceProvider.php new file mode 100644 index 0000000000..5092c6c77f --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/MetaProvider/MediaUtilitiesMetaServiceProvider.php @@ -0,0 +1,53 @@ +register(new ImagineServiceProvider()); + $app->register(new FFMpegServiceProvider()); + $app->register(new MediaAlchemystServiceProvider()); + $app->register(new PhraseanetMediaAlchemystServiceProvider()); + $app->register(new MediaVorusServiceProvider()); + $app->register(new MP4BoxServiceProvider()); + $app->register(new PHPExiftoolServiceProvider()); + + $app['imagine.factory'] = $app->share(function (Application $app) { + if ($app['conf']->get(['registry', 'executables', 'imagine-driver']) != '') { + return $app['conf']->get(['registry', 'executables', 'imagine-driver']); + } + + if (class_exists('\Gmagick')) { + return 'gmagick'; + } + + if (class_exists('\Imagick')) { + return 'imagick'; + } + + if (extension_loaded('gd')) { + return 'gd'; + } + + throw new \RuntimeException('No Imagine driver available'); + }); + } + + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Core/MetaProvider/TemplateEngineMetaProvider.php b/lib/Alchemy/Phrasea/Core/MetaProvider/TemplateEngineMetaProvider.php new file mode 100644 index 0000000000..15a2768f9e --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/MetaProvider/TemplateEngineMetaProvider.php @@ -0,0 +1,23 @@ +register(new TwigServiceProvider()); + $app->register(new PhraseanetTwigServiceProvider()); + } + + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Core/MetaProvider/TranslationMetaProvider.php b/lib/Alchemy/Phrasea/Core/MetaProvider/TranslationMetaProvider.php new file mode 100644 index 0000000000..a94ce2ad0b --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/MetaProvider/TranslationMetaProvider.php @@ -0,0 +1,39 @@ +register(new TranslationServiceProvider(), [ + 'locale_fallbacks' => ['fr'], + 'translator.resources' => [ + [ 'xlf', __DIR__.'/../../../../../resources/locales/messages.fr.xlf', 'fr', 'messages' ], + [ 'xlf', __DIR__.'/../../../../../resources/locales/validators.fr.xlf', 'fr', 'validators' ], + [ 'xlf', __DIR__.'/../../../../../resources/locales/messages.en.xlf', 'en', 'messages' ], + [ 'xlf', __DIR__.'/../../../../../resources/locales/validators.en.xlf', 'en', 'validators' ], + [ 'xlf', __DIR__.'/../../../../../resources/locales/messages.de.xlf', 'de', 'messages' ], + [ 'xlf', __DIR__.'/../../../../../resources/locales/validators.de.xlf', 'de', 'validators' ], + [ 'xlf', __DIR__.'/../../../../../resources/locales/messages.nl.xlf', 'nl', 'messages' ], + [ 'xlf', __DIR__.'/../../../../../resources/locales/validators.nl.xlf', 'nl', 'validators' ] + ], + 'translator.cache-options' => [ + 'debug' => $app['debug'], + 'cache_dir' => $app->share(function($app) { + return $app['cache.path'] . '/translations'; + }), + ], + ]); + } + + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Core/Middleware/SetupMiddlewareProvider.php b/lib/Alchemy/Phrasea/Core/Middleware/SetupMiddlewareProvider.php new file mode 100644 index 0000000000..fb4151bc42 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Middleware/SetupMiddlewareProvider.php @@ -0,0 +1,53 @@ +protect(function (Request $request) use ($app) { + if (0 === strpos($request->getPathInfo(), '/setup')) { + if (!$app['phraseanet.configuration-tester']->isInstalled()) { + if (!$app['phraseanet.configuration-tester']->isBlank()) { + if ('setup_upgrade_instructions' !== $app['request']->attributes->get('_route')) { + return $app->redirectPath('setup_upgrade_instructions'); + } + } + } elseif (!$app['phraseanet.configuration-tester']->isBlank()) { + return $app->redirectPath('homepage'); + } + } else { + if (false === strpos($request->getPathInfo(), '/include/minify')) { + $app['firewall']->requireSetup(); + } + } + }); + } + + /** + * Bootstraps the application. + * + * This method is called after all services are registered + * and should be used for "dynamic" configuration (whenever + * a service must be requested). + */ + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Core/Profiler/CacheDataCollector.php b/lib/Alchemy/Phrasea/Core/Profiler/CacheDataCollector.php new file mode 100644 index 0000000000..893c6bceb6 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Profiler/CacheDataCollector.php @@ -0,0 +1,132 @@ +statsListener = $cacheStatisticsSubscriber; + } + + /** + * Returns the name of the collector. + * + * @return string The collector name + */ + public function getName() + { + return 'cache'; + } + + /** + * Collects data for the given Request and Response. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * @param \Exception $exception An Exception instance + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->startProfile = new CacheProfile($this->statsListener->getInitialStats() ?: []); + $this->endProfile = new CacheProfile($this->statsListener->getCurrentStats() ?: []); + + $this->timeSpent = $this->statsListener->getTimeSpent(); + $this->calls = $this->statsListener->getCalls(); + $this->callSummary = $this->statsListener->getCallSummary(); + + $this->summary = new CacheProfileSummary( + $this->statsListener->getCacheType(), + $this->statsListener->getCacheNamespace(), + $this->startProfile, + $this->endProfile, + $this->callSummary + ); + } + + /** + * @return CacheProfile + */ + public function getInitialProfile() + { + return $this->startProfile; + } + + /** + * @return CacheProfile + */ + public function getCurrentProfile() + { + return $this->endProfile; + } + + /** + * @return int + */ + public function getTotalTime() + { + return $this->timeSpent; + } + + public function getCalls() + { + return $this->calls; + } + + public function getCallSummary() + { + return $this->callSummary; + } + + /** + * @return CacheProfileSummary + */ + public function getSummary() + { + return $this->summary; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Profiler/CacheProfile.php b/lib/Alchemy/Phrasea/Core/Profiler/CacheProfile.php new file mode 100644 index 0000000000..6544c88ba7 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Profiler/CacheProfile.php @@ -0,0 +1,74 @@ +serverStats = array_replace([ + 'hits' => 0, + 'misses' => 0, + 'uptime' => 0, + 'memory_usage' => 0, + 'memory_available' => 0 + ], $serverStats); + } + + /** + * @return int + */ + public function getHits() + { + return (int) $this->serverStats['hits']; + } + + /** + * @return int + */ + public function getMisses() + { + return (int) $this->serverStats['misses']; + } + + /** + * @return int + */ + public function getUptime() + { + return (int) $this->serverStats['uptime']; + } + + /** + * @return int + */ + public function getMemUsage() + { + return (int) $this->serverStats['memory_usage']; + } + + /** + * @return int + */ + public function getMemAvailable() + { + return (int) $this->serverStats['memory_available']; + } + + /** + * @return array + */ + public function getServerStats() + { + return $this->serverStats; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Profiler/CacheProfileSummary.php b/lib/Alchemy/Phrasea/Core/Profiler/CacheProfileSummary.php new file mode 100644 index 0000000000..80ab9d864e --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Profiler/CacheProfileSummary.php @@ -0,0 +1,153 @@ +cacheType = (string)$cacheType; + $this->cacheNamespace = (string)$namespace; + $this->initialProfile = $initialProfile; + $this->finalProfile = $finalProfile; + $this->callSummaryData = $callSummaryData; + } + + /** + * @return string + */ + public function getCacheType() + { + return $this->cacheType; + } + + /** + * @return string + */ + public function getNamespace() + { + return $this->cacheNamespace; + } + + /** + * @return int + */ + public function getHits() + { + if (isset($this->callSummaryData['hits'])) { + return (int) $this->callSummaryData['hits']; + } + + return (int)max(0, $this->finalProfile->getHits() - $this->initialProfile->getHits()); + } + + /** + * @return int + */ + public function getMisses() + { + if (isset($this->callSummaryData['misses'])) { + return (int) $this->callSummaryData['misses']; + } + + return (int)max(0, $this->finalProfile->getMisses() - $this->initialProfile->getMisses()); + } + + /** + * @return int + */ + public function getCalls() + { + if (isset($this->callSummaryData['calls'])) { + return (int) $this->callSummaryData['calls']; + } + + return $this->getHits() + $this->getMisses(); + } + + /** + * @return float + */ + public function getHitRatio() + { + $calls = $this->getCalls(); + + if ($calls == 0) { + return (float)0; + } + + return $this->getHits() / $calls; + } + + /** + * @return float + */ + public function getMissRatio() + { + $calls = $this->getCalls(); + + if ($calls == 0) { + return (float)0; + } + + return $this->getMisses() / $calls; + } + + /** + * @return int + */ + public function getMemUsageDelta() + { + return $this->finalProfile->getMemUsage() - $this->initialProfile->getMemUsage(); + } + + /** + * @return int + */ + public function getMemAvailableDelta() + { + return $this->finalProfile->getMemAvailable() - $this->initialProfile->getMemAvailable(); + } + + public function getUptimeDelta() + { + return $this->finalProfile->getUptime() - $this->initialProfile->getUptime(); + } +} diff --git a/lib/Alchemy/Phrasea/Core/Profiler/TraceableCache.php b/lib/Alchemy/Phrasea/Core/Profiler/TraceableCache.php new file mode 100644 index 0000000000..d465220a17 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Profiler/TraceableCache.php @@ -0,0 +1,341 @@ + 0, + 'hits' => 0, + 'misses' => 0, + 'calls_by_type' => [], + 'calls_by_key' => [], + ]; + + private $stopWatch; + + /*s* + * @param PhraseaCache $cache + */ + public function __construct(PhraseaCache $cache, Stopwatch $stopwatch = null) + { + $this->cache = $cache; + $this->stopWatch = $stopwatch ?: new Stopwatch(); + } + + private function collect($type, $id, $hit = true, $result = null) + { + $this->summary['calls']++; + $this->summary['hits'] += $hit ? 1 : 0; + $this->summary['misses'] += $hit ? 0 : 1; + + if (! array_key_exists($type, $this->summary['calls_by_type'])) { + $this->summary['calls_by_type'][$type] = 0; + } + + $this->summary['calls_by_type'][$type]++; + + if (! array_key_exists($id, $this->summary['calls_by_key'])) { + $this->summary['calls_by_key'][$id] = [ + 'total' => 0, + 'reads'=> 0, + 'writes' => 0, + 'hits' => 0, + 'misses' => 0 + ]; + } + + if (! array_key_exists($type, $this->summary['calls_by_key'][$id])) { + $this->summary['calls_by_key'][$id][$type] = 0; + } + $this->summary['calls_by_key'][$id]['hits'] += $hit ? 1 : 0; + $this->summary['calls_by_key'][$id]['misses'] += $hit ? 0 : 1; + + $this->summary['calls_by_key'][$id]['reads'] += ($type == 'fetch' || $type == 'contains') ? 1 : 0; + $this->summary['calls_by_key'][$id]['writes'] += ($type == 'fetch' || $type == 'contains') ? 0 : 1; + + $this->summary['calls_by_key'][$id]['total']++; + + $lastCall = end($this->calls); + $callData = [ + 'type' => $type, + 'key' => $id, + 'result' => $result, + 'hit' => (bool) $hit, + 'count' => 1 + ]; + + if (is_array($lastCall) && $this->compareCalls($lastCall, $callData)) { + $lastCall['count']++; + $callData = $lastCall; + + array_pop($this->calls); + } + + $this->calls[] = $callData; + } + + private function compareCalls(array $previousCall, array $currentCall) + { + $keys = [ 'type', 'key', 'result', 'hit']; + + foreach ($keys as $key) { + if ($previousCall[$key] != $currentCall[$key]) { + return false; + } + } + + return true; + } + + /** + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return int + */ + public function getTotalTime() + { + return $this->stopWatch->getEvent('cache')->getDuration(); + } + + /** + * @return array + */ + public function getCalls() + { + return $this->calls; + } + + /** + * @return array + */ + public function getSummary() + { + return $this->summary; + } + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed The cached data or FALSE, if no cache entry exists for the given id. + */ + public function fetch($id) + { + try { + $this->stopWatch->start('cache'); + $value = $this->cache->fetch($id); + $this->stopWatch->stop('cache'); + } + catch (\Exception $ex) { + $value = false; + } + + $this->collect('fetch', $id, $value != false, $value); + + return $value; + } + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + public function contains($id) + { + $this->collect('contains', $id); + + $this->stopWatch->start('cache'); + $result = $this->cache->contains($id); + $this->stopWatch->stop('cache'); + + return $result; + } + + /** + * Puts data into the cache. + * + * If a cache entry with the given id already exists, its data will be replaced. + * + * @param string $id The cache id. + * @param mixed $data The cache entry/data. + * @param int $lifeTime The lifetime in number of seconds for this cache entry. + * If zero (the default), the entry never expires (although it may be deleted from the cache + * to make place for other entries). + * + * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + public function save($id, $data, $lifeTime = 0) + { + $this->collect('save', $id); + + $this->stopWatch->start('cache'); + $result = $this->cache->save($id, $data, $lifeTime); + $this->stopWatch->stop('cache'); + + return $result; + } + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. + * Deleting a non-existing entry is considered successful. + */ + public function delete($id) + { + $this->collect('delete', $id); + + $this->stopWatch->start('cache'); + $result = $this->cache->delete($id); + $this->stopWatch->stop('cache'); + + return $result; + } + + /** + * Retrieves cached information from the data store. + * + * The server's statistics array has the following values: + * + * - hits + * Number of keys that have been requested and found present. + * + * - misses + * Number of items that have been requested and not found. + * + * - uptime + * Time that the server is running. + * + * - memory_usage + * Memory used by this server to store items. + * + * - memory_available + * Memory allowed to use for storage. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + public function getStats() + { + $this->stopWatch->start('cache'); + $result = $this->cache->getStats(); + $this->stopWatch->stop('cache'); + + return $result; + } + + /** + * Sets the namespace + * + * @param string $namespace + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + $this->cache->setNamespace($namespace); + } + + /** + * Flushes all data contained in the adapter + */ + public function flushAll() + { + $this->collect('flush-all', null); + $this->stopWatch->start('cache'); + $this->cache->flushAll(); + $this->stopWatch->stop('cache'); + } + + /** + * Name of the cache driver + * @return string + */ + public function getName() + { + return 'traceable-' . $this->cache->getName(); + } + + /** + * Tell whether the caching system use a server or not + * @return boolean + */ + public function isServer() + { + return $this->cache->isServer(); + } + + /** + * Tell if the cache system is online + * @return boolean + */ + public function isOnline() + { + return $this->cache->isOnline(); + } + + /** + * Get an entry from the cache. + * + * @param string $key cache id The id of the cache entry to fetch. + * + * @return string The cached data. + * @return FALSE, if no cache entry exists for the given id. + * + * @throws Exception if provided key does not exist + */ + public function get($key) + { + if ( ! $this->contains($key)) { + throw new Exception('Unable to retrieve the value'); + } + + return $this->fetch($key); + } + + /** + * Delete multi cache entries + * + * @param array $keys contains all keys to delete + * @return PhraseaCache + */ + public function deleteMulti(array $keys) + { + foreach ($keys as $key) { + $this->delete($key); + } + } +} diff --git a/lib/Alchemy/Phrasea/Core/Provider/BorderManagerServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/BorderManagerServiceProvider.php index 496c56fde5..36f0ed0e7e 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/BorderManagerServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/BorderManagerServiceProvider.php @@ -70,7 +70,7 @@ class BorderManagerServiceProvider implements ServiceProviderInterface $collections = []; foreach ($checker['collections'] as $base_id) { try { - $collections[] = \collection::get_from_base_id($app, $base_id); + $collections[] = \collection::getByBaseId($app, $base_id); } catch (\Exception $e) { throw new \InvalidArgumentException('Invalid collection option'); } diff --git a/lib/Alchemy/Phrasea/Core/Provider/ConfigurationServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/ConfigurationServiceProvider.php index a684fa6412..55623e0081 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/ConfigurationServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/ConfigurationServiceProvider.php @@ -69,7 +69,7 @@ class ConfigurationServiceProvider implements ServiceProviderInterface }); $app['conf.restrictions'] = $app->share(function (SilexApplication $app) { - return new AccessRestriction($app['cache'], $app['conf'], $app->getApplicationBox(), $app['monolog']); + return new AccessRestriction($app['conf'], $app->getApplicationBox(), $app['monolog']); }); } diff --git a/lib/Alchemy/Phrasea/Core/Provider/ConfigurationTesterServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/ConfigurationTesterServiceProvider.php index 57e0ee583f..188e39eda8 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/ConfigurationTesterServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/ConfigurationTesterServiceProvider.php @@ -15,6 +15,7 @@ use Alchemy\Phrasea\Setup\ConfigurationTester; use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\PreSchemaUpgradeCollection; use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Feeds; +use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Sessions; use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Tokens; use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Users; use Silex\Application as SilexApplication; @@ -30,7 +31,7 @@ class ConfigurationTesterServiceProvider implements ServiceProviderInterface }); $app['phraseanet.pre-schema-upgrader.upgrades'] = $app->share(function () { - return [new Upgrade39Feeds(), new Upgrade39Users(), new Upgrade39Tokens()]; + return [new Upgrade39Feeds(), new Upgrade39Users(), new Upgrade39Tokens(), new Upgrade39Sessions()]; }); $app['phraseanet.pre-schema-upgrader'] = $app->share(function (Application $app) { diff --git a/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php index 3333b45090..ab651780bd 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php @@ -14,12 +14,11 @@ namespace Alchemy\Phrasea\Core\Provider; use Alchemy\Phrasea\Core\Event\Subscriber\XSendFileSubscriber; use Alchemy\Phrasea\Http\H264PseudoStreaming\H264Factory; use Alchemy\Phrasea\Http\ServeFileResponseFactory; -use Alchemy\Phrasea\Http\StaticFile\StaticFileFactory; use Alchemy\Phrasea\Http\StaticFile\StaticMode; -use Alchemy\Phrasea\Http\StaticFile\Symlink\SymLinker; use Alchemy\Phrasea\Http\XSendFile\XSendFileFactory; use Silex\Application; use Silex\ServiceProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class FileServeServiceProvider implements ServiceProviderInterface { @@ -41,11 +40,11 @@ class FileServeServiceProvider implements ServiceProviderInterface }); $app['phraseanet.static-file'] = $app->share(function (Application $app) { - return new StaticMode(SymLinker::create($app)); + return new StaticMode($app['phraseanet.thumb-symlinker']); }); $app['phraseanet.file-serve'] = $app->share(function (Application $app) { - return ServeFileResponseFactory::create($app); + return new ServeFileResponseFactory($app['unicode']); }); } @@ -55,7 +54,7 @@ class FileServeServiceProvider implements ServiceProviderInterface public function boot(Application $app) { $app['dispatcher'] = $app->share( - $app->extend('dispatcher', function ($dispatcher, Application $app) { + $app->extend('dispatcher', function (EventDispatcherInterface $dispatcher, Application $app) { $dispatcher->addSubscriber(new XSendFileSubscriber($app)); return $dispatcher; diff --git a/lib/Alchemy/Phrasea/Core/Provider/MediaAlchemystServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/MediaAlchemystServiceProvider.php new file mode 100644 index 0000000000..dd4e970b6f --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Provider/MediaAlchemystServiceProvider.php @@ -0,0 +1,53 @@ +share(function (Application $app) { + $configuration = []; + $parameters = [ + 'swftools.pdf2swf.binaries' => 'pdf2swf_binary', + 'swftools.swfrender.binaries' => 'swf_render_binary', + 'swftools.swfextract.binaries' => 'swf_extract_binary', + 'unoconv.binaries' => 'unoconv_binary', + 'mp4box.binaries' => 'mp4box_binary', + 'gs.binaries' => 'ghostscript_binary', + 'ffmpeg.ffmpeg.binaries' => 'ffmpeg_binary', + 'ffmpeg.ffprobe.binaries' => 'ffprobe_binary', + 'ffmpeg.ffmpeg.timeout' => 'ffmpeg_timeout', + 'ffmpeg.ffprobe.timeout' => 'ffprobe_timeout', + 'gs.timeout' => 'gs_timeout', + 'mp4box.timeout' => 'mp4box_timeout', + 'swftools.timeout' => 'swftools_timeout', + 'unoconv.timeout' => 'unoconv_timeout', + ]; + + foreach ($parameters as $parameter => $key) { + if ($app['conf']->has(['main', 'binaries', $key])) { + $configuration[$parameter] = $app['conf']->get(['main', 'binaries', $key]); + } + } + + $configuration['ffmpeg.threads'] = $app['conf']->get(['registry', 'executables', 'ffmpeg-threads']) ?: null; + $configuration['imagine.driver'] = $app['conf']->get(['registry', 'executables', 'imagine-driver']) ?: null; + + return $configuration; + }); + + $app['media-alchemyst.logger'] = $app->share(function (Application $app) { + return $app['monolog']; + }); + } + + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Core/Provider/PhraseanetServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/PhraseanetServiceProvider.php index 988c1a850d..e0f477f17a 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/PhraseanetServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/PhraseanetServiceProvider.php @@ -41,11 +41,15 @@ class PhraseanetServiceProvider implements ServiceProviderInterface }); $app['phraseanet.thumb-symlinker'] = $app->share(function (SilexApplication $app) { - return SymLinker::create($app); + return new SymLinker( + $app['phraseanet.thumb-symlinker-encoder'], + $app['filesystem'], + $app['thumbnail.path'] + ); }); $app['phraseanet.thumb-symlinker-encoder'] = $app->share(function (SilexApplication $app) { - return SymLinkerEncoder::create($app); + return new SymLinkerEncoder($app['phraseanet.configuration']['main']['key']); }); $app['acl'] = $app->share(function (SilexApplication $app) { diff --git a/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php index fe40727b0c..5e50367f09 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php @@ -1,9 +1,8 @@ getRepository('Phraseanet:Registration'); }); $app['repo.baskets'] = $app->share(function (PhraseaApplication $app) { - return $app['orm.em']->getRepository('Phraseanet:Basket'); + /** @var BasketRepository $repository */ + $repository = $app['orm.em']->getRepository('Phraseanet:Basket'); + $repository->setTranslator($app['translator']); + + return $repository; }); $app['repo.basket-elements'] = $app->share(function (PhraseaApplication $app) { return $app['orm.em']->getRepository('Phraseanet:BasketElement'); @@ -133,11 +146,21 @@ class RepositoriesServiceProvider implements ServiceProviderInterface }); $app['repo.databoxes'] = $app->share(function (PhraseaApplication $app) { - $factory = new DataboxFactory($app); $appbox = $app->getApplicationBox(); - $repository = new DbalDataboxRepository($appbox->get_connection(), $factory); - return new CachingDataboxRepositoryDecorator($repository, $app['cache'], $appbox->get_cache_key($appbox::CACHE_LIST_BASES), $factory); + $factory = new DataboxFactory($app); + $repository = new CachingDataboxRepositoryDecorator( + new DbalDataboxRepository($appbox->get_connection(), $factory), + $app['cache'], + $appbox->get_cache_key($appbox::CACHE_LIST_BASES), + $factory + ); + + $repository = new ArrayCacheDataboxRepository($repository); + + $factory->setDataboxRepository($repository); + + return $repository; }); $app['repo.fields.factory'] = $app->protect(function (\databox $databox) use ($app) { @@ -147,9 +170,37 @@ class RepositoriesServiceProvider implements ServiceProviderInterface $app['repo.records.factory'] = $app->protect(function (\databox $databox) use ($app) { return new LegacyRecordRepository($app, $databox); }); + + $app['repo.collection-references'] = $app->share(function (PhraseaApplication $app) { + $repository = new DbalCollectionReferenceRepository($app->getApplicationBox()->get_connection()); + + return new ArrayCacheCollectionReferenceRepository($repository); + }); + $app['repo.collections-registry'] = $app->share(function (PhraseaApplication $app) { + $factory = new CollectionFactory($app); + $connectionProvider = new DataboxConnectionProvider($app->getApplicationBox()); + + $repositoryFactory = new DbalCollectionRepositoryFactory( + $connectionProvider, + $factory, + $app['repo.collection-references'] + ); + + $repositoryFactory = new CachedCollectionRepositoryFactory( + $app, + $repositoryFactory, + $app['cache'], + 'phrasea.collections' + ); + + $repositoryFactory = new ArrayCachedCollectionRepositoryFactory($repositoryFactory); + + return new CollectionRepositoryRegistry($app, $repositoryFactory, $app['repo.collection-references']); + }); } public function boot(Application $app) { + // no-op } } diff --git a/lib/Alchemy/Phrasea/Core/Provider/SubdefServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/SubdefServiceProvider.php index 6210c72e5e..1ef91b9bba 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/SubdefServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/SubdefServiceProvider.php @@ -21,11 +21,14 @@ class SubdefServiceProvider implements ServiceProviderInterface { public function register(SilexApplication $app) { - $app['subdef.generator'] = $app->share(function (SilexApplication $app) { - return new SubdefGenerator($app, $app['media-alchemyst'], $app['filesystem'], $app['mediavorus'], isset($app['task-manager.logger']) ? $app['task-manager.logger'] : $app['monolog']); + $app['subdef.generator'] = $app->share(function (Application $app) { + $generator = new SubdefGenerator($app, $app['media-alchemyst'], $app['phraseanet.filesystem'], $app['mediavorus'], isset($app['task-manager.logger']) ? $app['task-manager.logger'] : $app['monolog']); + $generator->setDispatcher($app['dispatcher']); + + return $generator; }); - $app['subdef.substituer'] = $app->share(function (SilexApplication $app) { - return new SubdefSubstituer($app, $app['filesystem'], $app['media-alchemyst'], $app['mediavorus'], $app['dispatcher']); + $app['subdef.substituer'] = $app->share(function (Application $app) { + return new SubdefSubstituer($app, $app['phraseanet.filesystem'], $app['media-alchemyst'], $app['mediavorus'], $app['dispatcher']); }); } diff --git a/lib/Alchemy/Phrasea/Core/Provider/TranslationServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/TranslationServiceProvider.php index c50cd00bf6..f87c421728 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/TranslationServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/TranslationServiceProvider.php @@ -5,6 +5,7 @@ namespace Alchemy\Phrasea\Core\Provider; use Alchemy\Phrasea\Utilities\CachedTranslator; use Silex\Application; use Silex\ServiceProviderInterface; +use Symfony\Component\Translation\Loader\PoFileLoader; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\Loader\ArrayLoader; use JMS\TranslationBundle\Translation\Loader\Symfony\XliffLoader; @@ -36,6 +37,7 @@ class TranslationServiceProvider implements ServiceProviderInterface $translator->addLoader('xliff', new XliffLoader()); // to load Phraseanet resources $translator->addLoader('xlf', new XliffLoader()); + $translator->addLoader('po', new PoFileLoader()); foreach ($app['translator.domains'] as $domain => $data) { foreach ($data as $locale => $messages) { @@ -43,6 +45,15 @@ class TranslationServiceProvider implements ServiceProviderInterface } } + foreach ($app['translator.resources'] as $resource) { + $translator->addResource( + $resource[0], + $resource[1], + $resource[2], + isset($resource[3]) ? $resource[3] : null + ); + } + return $translator; }); diff --git a/lib/Alchemy/Phrasea/Core/Provider/TwigServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/TwigServiceProvider.php new file mode 100644 index 0000000000..6fa067a84c --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Provider/TwigServiceProvider.php @@ -0,0 +1,159 @@ +share($app->extend('twig', function (\Twig_Environment $twig, $app) { + $twig->setCache($app['cache.path'] . '/twig'); + + $paths = []; + + if (file_exists($app['plugin.path'] . '/twig-paths.php')) { + $paths = require $app['plugin.path'] . '/twig-paths.php'; + } + + if ($app['browser']->isTablet() || $app['browser']->isMobile()) { + $paths[] = $app['root.path'] . '/config/templates/mobile'; + $paths[] = $app['root.path'] . '/templates/mobile'; + $paths['phraseanet'] = $app['root.path'] . '/config/templates/mobile'; + $paths['phraseanet'] = $app['root.path'] . '/templates/mobile'; + } + + $paths[] = $app['root.path'] . '/config/templates/web'; + $paths[] = $app['root.path'] . '/templates/web'; + $paths['phraseanet'] = $app['root.path'] . '/config/templates/web'; + $paths['phraseanet'] = $app['root.path'] . '/templates/web'; + + foreach ($paths as $namespace => $path) { + if (!is_int($namespace)) { + $app['twig.loader.filesystem']->addPath($path, $namespace); + } else { + $app['twig.loader.filesystem']->addPath($path); + } + } + + $twig->addGlobal('current_date', new \DateTime()); + + $this->registerExtensions($twig, $app); + $this->registerFilters($twig, $app); + + return $twig; + })); + } + + private function registerExtensions(\Twig_Environment $twig, Application $app) + { + $twig->addExtension(new \Twig_Extension_Core()); + $twig->addExtension(new \Twig_Extension_Optimizer()); + $twig->addExtension(new \Twig_Extension_Escaper()); + if ($app['debug']) { + $twig->addExtension(new \Twig_Extension_Debug()); + } + + // add filter trans + $twig->addExtension(new TranslationExtension($app['translator'])); + // add filter localizeddate + $twig->addExtension(new \Twig_Extensions_Extension_Intl()); + // add filters truncate, wordwrap, nl2br + $twig->addExtension(new \Twig_Extensions_Extension_Text()); + $twig->addExtension(new JSUniqueID()); + $twig->addExtension(new Fit()); + $twig->addExtension(new Camelize()); + $twig->addExtension(new BytesConverter()); + $twig->addExtension(new PhraseanetExtension($app)); + } + + private function registerFilters(\Twig_Environment $twig, Application $app) + { + $twig->addFilter('serialize', new \Twig_Filter_Function('serialize')); + $twig->addFilter('stristr', new \Twig_Filter_Function('stristr')); + $twig->addFilter('get_class', new \Twig_Filter_Function('get_class')); + $twig->addFilter('stripdoublequotes', new \Twig_Filter_Function('stripdoublequotes')); + $twig->addFilter('get_collection_logo', new \Twig_Filter_Function('collection::getLogo')); + $twig->addFilter('floor', new \Twig_Filter_Function('floor')); + $twig->addFilter('ceil', new \Twig_Filter_Function('ceil')); + $twig->addFilter('max', new \Twig_Filter_Function('max')); + $twig->addFilter('min', new \Twig_Filter_Function('min')); + $twig->addFilter('bas_labels', new \Twig_Filter_Function('phrasea::bas_labels')); + $twig->addFilter('sbas_names', new \Twig_Filter_Function('phrasea::sbas_names')); + $twig->addFilter('sbas_labels', new \Twig_Filter_Function('phrasea::sbas_labels')); + $twig->addFilter('sbas_from_bas', new \Twig_Filter_Function('phrasea::sbasFromBas')); + $twig->addFilter('key_exists', new \Twig_Filter_Function('array_key_exists')); + $twig->addFilter('round', new \Twig_Filter_Function('round')); + $twig->addFilter('count', new \Twig_Filter_Function('count')); + $twig->addFilter('formatOctets', new \Twig_Filter_Function('p4string::format_octets')); + $twig->addFilter('base_from_coll', new \Twig_Filter_Function('phrasea::baseFromColl')); + $twig->addFilter(new \Twig_SimpleFilter('escapeSimpleQuote', function ($value) { + return str_replace("'", "\\'", $value); + })); + + $twig->addFilter(new \Twig_SimpleFilter('highlight', function (\Twig_Environment $twig, $string) { + return str_replace(['[[em]]', '[[/em]]'], ['', ''], $string); + }, ['needs_environment' => true, 'is_safe' => ['html']])); + + $twig->addFilter(new \Twig_SimpleFilter('linkify', function (\Twig_Environment $twig, $string) use ($app) { + return preg_replace( + "(([^']{1})((https?|file):((/{2,4})|(\\{2,4}))[\w:#%/;$()~_?/\-=\\\.&]*)([^']{1}))" + , + '$1 $2  $7' + , $string + ); + }, ['needs_environment' => true, 'is_safe' => ['html']])); + + $twig->addFilter(new \Twig_SimpleFilter('bounce', + function (\Twig_Environment $twig, $fieldValue, $fieldName, $searchRequest, $sbasId) { + // bounce value if it is present in thesaurus as well + return "" + . $fieldValue + . ""; + + }, ['needs_environment' => true, 'is_safe' => ['html']])); + + $twig->addFilter(new \Twig_SimpleFilter('escapeDoubleQuote', function ($value) { + return str_replace('"', '\"', $value); + })); + } + + /** + * Bootstraps the application. + * + * This method is called after all services are registered + * and should be used for "dynamic" configuration (whenever + * a service must be requested). + */ + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Core/Provider/WebProfilerServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/WebProfilerServiceProvider.php new file mode 100644 index 0000000000..d6fd0d25f8 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Provider/WebProfilerServiceProvider.php @@ -0,0 +1,133 @@ +share( + $app->extend('web_profiler.toolbar.listener', function () use ($app) { + return new WebDebugToolbarListener( + $app['twig'], + $app['web_profiler.debug_toolbar.intercept_redirects'], + 2, + $app['web_profiler.debug_toolbar.position'], + $app['url_generator'] + ); + }) + ); + + if (class_exists(AjaxDataCollector::class)) { + $app['data_collector.templates'] = $app->share($app->extend('data_collector.templates', function (array $templates) { + $templates[] = array('ajax', '@WebProfiler/Collector/ajax.html.twig'); + + return $templates; + })); + + $app['data_collectors'] = $app->share($app->extend('data_collectors', function ($collectors) use ($app) { + $collectors['ajax'] = function () { + return new AjaxDataCollector(); + }; + + return $collectors; + })); + } + + $app['data_collectors.doctrine'] = $app->share(function ($app) { + /** @var Connection $db */ + $db = $app['db']; + return new DoctrineDataCollector($db); + }); + + $app['cache'] = $app->share($app->extend('cache', function (Cache $cache, $app) { + $namespace = $app['conf']->get(['main', 'cache', 'options', 'namespace']); + $cache = new TraceableCache($cache); + + $cache->setNamespace($namespace); + + return $cache; + })); + + $app['data_collector.cache_subscriber'] = $app->share(function ($app) { + return new CacheStatisticsSubscriber($app['cache']); + }); + + $app['data_collectors'] = $app->share($app->extend('data_collectors', function ($collectors) use ($app) { + $collectors['db'] = $app->share(function ($app) { + /** @var DoctrineDataCollector $collector */ + $collector = $app['data_collectors.doctrine']; + + $loggerChain = new LoggerChain(); + $logger = new DebugStack(); + + $loggerChain->addLogger($logger); + $loggerChain->addLogger(new DbalLogger($app['logger'], $app['stopwatch'])); + + /** @var Connection $db */ + $db = $app['db']; + $db->getConfiguration()->setSQLLogger($loggerChain); + + $collector->addLogger($logger); + + return $collector; + }); + + $collectors['cache'] = $app->share(function ($app) { + return new CacheDataCollector($app['data_collector.cache_subscriber']); + }); + + return $collectors; + })); + + $app['data_collector.templates'] = $app->share($app->extend('data_collector.templates', function (array $templates) { + $templates[] = array('db', '@DoctrineBundle/Collector/db.html.twig'); + $templates[] = array('cache', '@PhraseaProfiler/cache.html.twig'); + + return $templates; + })); + + $app['twig.loader.filesystem'] = $app->share($app->extend('twig.loader.filesystem', function ($loader, $app) { + $loader->addPath( + $app['root.path'] . '/vendor/sorien/silex-dbal-profiler/src/Sorien/Resources/views', + 'DoctrineBundle' + ); + $loader->addPath($app['root.path'] . '/templates-profiler/', 'PhraseaProfiler'); + return $loader; + })); + } + + /** + * Bootstraps the application. + * + * This method is called after all services are registered + * and should be used for "dynamic" configuration (whenever + * a service must be requested). + */ + public function boot(Application $app) + { + $app['dispatcher']->addSubscriber($app['data_collector.cache_subscriber']); + } +} diff --git a/lib/Alchemy/Phrasea/Core/Provider/WebhookServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/WebhookServiceProvider.php new file mode 100644 index 0000000000..e224d8a43d --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Provider/WebhookServiceProvider.php @@ -0,0 +1,23 @@ +share(function ($app) { + return new EventProcessorFactory($app); + }); + } + + public function boot(Application $app) + { + // no-op + } +} diff --git a/lib/Alchemy/Phrasea/Databox/ArrayCacheDataboxRepository.php b/lib/Alchemy/Phrasea/Databox/ArrayCacheDataboxRepository.php new file mode 100644 index 0000000000..7660771460 --- /dev/null +++ b/lib/Alchemy/Phrasea/Databox/ArrayCacheDataboxRepository.php @@ -0,0 +1,136 @@ +repository = $repository; + } + + /** + * @param int $id + * @return \databox + */ + public function find($id) + { + $this->load(); + + if (! isset($this->databoxes[$id])) { + return null; + } + + return $this->databoxes[$id]; + } + + /** + * @return \databox[] + */ + public function findAll() + { + $this->load(); + + return $this->databoxes; + } + + /** + * @param \databox $databox + */ + public function save(\databox $databox) + { + $this->clear(); + + return $this->repository->save($databox); + } + + /** + * @param \databox $databox + */ + public function delete(\databox $databox) + { + $this->clear(); + + return $this->repository->delete($databox); + } + + /** + * @param \databox $databox + */ + public function unmount(\databox $databox) + { + $this->clear(); + + return $this->repository->unmount($databox); + } + + /** + * @param $host + * @param $port + * @param $user + * @param $password + * @param $dbname + * + * @return \databox + */ + public function mount($host, $port, $user, $password, $dbname) + { + $this->clear(); + + return $this->repository->mount($host, $port, $user, $password, $dbname); + } + + /** + * @param $host + * @param $port + * @param $user + * @param $password + * @param $dbname + * + * @return \databox + */ + public function create($host, $port, $user, $password, $dbname) + { + $this->clear(); + + return $this->repository->create($host, $port, $user, $password, $dbname); + } + + /** + * Initializes the memory cache if needed. + */ + private function load() + { + if (! $this->loaded) { + $this->databoxes = $this->repository->findAll(); + $this->loaded = true; + } + } + + /** + * Clears the memory cache. + */ + private function clear() + { + $this->loaded = false; + $this->databoxes = []; + } +} diff --git a/lib/Alchemy/Phrasea/Databox/CachingDataboxRepositoryDecorator.php b/lib/Alchemy/Phrasea/Databox/CachingDataboxRepositoryDecorator.php index 6f94cedb22..14dcf824e5 100644 --- a/lib/Alchemy/Phrasea/Databox/CachingDataboxRepositoryDecorator.php +++ b/lib/Alchemy/Phrasea/Databox/CachingDataboxRepositoryDecorator.php @@ -22,6 +22,12 @@ final class CachingDataboxRepositoryDecorator implements DataboxRepository /** @var DataboxFactory */ private $factory; + /** + * @param DataboxRepository $repository + * @param Cache $cache + * @param string $cacheKey + * @param DataboxFactory $factory + */ public function __construct(DataboxRepository $repository, Cache $cache, $cacheKey, DataboxFactory $factory) { $this->repository = $repository; @@ -56,6 +62,63 @@ final class CachingDataboxRepositoryDecorator implements DataboxRepository return $databoxes; } + public function save(\databox $databox) + { + $this->clearCache(); + + return $this->repository->save($databox); + } + + public function delete(\databox $databox) + { + $this->clearCache(); + + return $this->repository->delete($databox); + } + + public function unmount(\databox $databox) + { + $this->clearCache(); + + return $this->repository->unmount($databox); + } + + /** + * @param $host + * @param $port + * @param $user + * @param $password + * @param $dbname + * + * @return \databox + */ + public function mount($host, $port, $user, $password, $dbname) + { + $databox = $this->repository->mount($host, $port, $user, $password, $dbname); + + $this->clearCache(); + + return $databox; + } + + /** + * @param $host + * @param $port + * @param $user + * @param $password + * @param $dbname + * + * @return \databox + */ + public function create($host, $port, $user, $password, $dbname) + { + $databox = $this->repository->create($host, $port, $user, $password, $dbname); + + $this->clearCache(); + + return $databox; + } + /** * @param \databox[] $databoxes */ @@ -69,4 +132,9 @@ final class CachingDataboxRepositoryDecorator implements DataboxRepository $this->cache->save($this->cacheKey, $rows); } + + private function clearCache() + { + $this->cache->delete($this->cacheKey); + } } diff --git a/lib/Alchemy/Phrasea/Databox/DataboxConnectionProvider.php b/lib/Alchemy/Phrasea/Databox/DataboxConnectionProvider.php new file mode 100644 index 0000000000..a1601dd00d --- /dev/null +++ b/lib/Alchemy/Phrasea/Databox/DataboxConnectionProvider.php @@ -0,0 +1,23 @@ +applicationBox = $applicationBox; + } + + /** + * @param $databoxId + * @return \Doctrine\DBAL\Connection + */ + public function getConnection($databoxId) + { + return $this->applicationBox->get_databox($databoxId)->get_connection(); + } +} diff --git a/lib/Alchemy/Phrasea/Databox/DataboxFactory.php b/lib/Alchemy/Phrasea/Databox/DataboxFactory.php index 15b38281c6..522dd989f7 100644 --- a/lib/Alchemy/Phrasea/Databox/DataboxFactory.php +++ b/lib/Alchemy/Phrasea/Databox/DataboxFactory.php @@ -17,20 +17,33 @@ class DataboxFactory /** @var Application */ private $app; + /** @var DataboxRepository */ + private $databoxRepository; + + /** + * @param Application $app + */ public function __construct(Application $app) { $this->app = $app; } /** - * @param int $id + * @param DataboxRepository $databoxRepository + */ + public function setDataboxRepository(DataboxRepository $databoxRepository) + { + $this->databoxRepository = $databoxRepository; + } + + /** + * @param int $id * @param array $raw - * @throws NotFoundHttpException when Databox could not be retrieved from Persistence layer - * @return \databox + * @return \databox when Databox could not be retrieved from Persistence layer */ public function create($id, array $raw) { - return new \databox($this->app, $id, $raw); + return new \databox($this->app, $id, $this->databoxRepository, $raw); } /** @@ -43,7 +56,7 @@ class DataboxFactory $databoxes = []; foreach ($rows as $id => $raw) { - $databoxes[$id] = new \databox($this->app, $id, $raw); + $databoxes[$id] = new \databox($this->app, $id, $this->databoxRepository, $raw); } return $databoxes; diff --git a/lib/Alchemy/Phrasea/Databox/DataboxPathExtractor.php b/lib/Alchemy/Phrasea/Databox/DataboxPathExtractor.php new file mode 100644 index 0000000000..f3e4a82e95 --- /dev/null +++ b/lib/Alchemy/Phrasea/Databox/DataboxPathExtractor.php @@ -0,0 +1,37 @@ +appbox = $appbox; + } + + public function extractPaths() + { + $paths = []; + + foreach ($this->appbox->get_databoxes() as $databox) { + foreach ($databox->get_subdef_structure()->getSubdefGroup('video') as $subdef) { + $paths[] = $subdef->get_path(); + } + } + + return array_filter(array_unique($paths)); + } +} diff --git a/lib/Alchemy/Phrasea/Databox/DataboxRepository.php b/lib/Alchemy/Phrasea/Databox/DataboxRepository.php index 73a4ccbb5d..a8d84f5fc9 100644 --- a/lib/Alchemy/Phrasea/Databox/DataboxRepository.php +++ b/lib/Alchemy/Phrasea/Databox/DataboxRepository.php @@ -21,4 +21,41 @@ interface DataboxRepository * @return \databox[] */ public function findAll(); + + /** + * @param \databox $databox + */ + public function save(\databox $databox); + + /** + * @param \databox $databox + */ + public function delete(\databox $databox); + + /** + * @param \databox $databox + */ + public function unmount(\databox $databox); + + /** + * @param $host + * @param $port + * @param $user + * @param $password + * @param $dbname + * + * @return \databox + */ + public function mount($host, $port, $user, $password, $dbname); + + /** + * @param $host + * @param $port + * @param $user + * @param $password + * @param $dbname + * + * @return \databox + */ + public function create($host, $port, $user, $password, $dbname); } diff --git a/lib/Alchemy/Phrasea/Databox/DbalDataboxRepository.php b/lib/Alchemy/Phrasea/Databox/DbalDataboxRepository.php index 45efe87112..5ce27cb846 100644 --- a/lib/Alchemy/Phrasea/Databox/DbalDataboxRepository.php +++ b/lib/Alchemy/Phrasea/Databox/DbalDataboxRepository.php @@ -47,6 +47,25 @@ final class DbalDataboxRepository implements DataboxRepository return $this->factory->createMany($this->fetchRows()); } + /** + * @param \databox $databox + * @return bool + */ + public function save(\databox $databox) + { + return true; + } + + public function delete(\databox $databox) + { + return true; + } + + public function unmount(\databox $databox) + { + return true; + } + /** * @param int $id * @return false|array @@ -82,4 +101,73 @@ final class DbalDataboxRepository implements DataboxRepository return $rows; } + + /** + * @param $host + * @param $port + * @param $user + * @param $password + * @param $dbname + * + * @return \databox + */ + public function mount($host, $port, $user, $password, $dbname) + { + $query = 'INSERT INTO sbas (ord, host, port, dbname, sqlengine, user, pwd) + SELECT COALESCE(MAX(ord), 0) + 1 AS ord, :host AS host, :port AS port, :dbname AS dbname, + "MYSQL" AS sqlengine, :user AS user, :password AS pwd FROM sbas'; + + $statement = $this->connection->prepare($query); + $statement->execute([ + ':host' => $host, + ':port' => $port, + ':dbname' => $dbname, + ':user' => $user, + ':password' => $password + ]); + + $statement->closeCursor(); + + return $this->find((int) $this->connection->lastInsertId()); + } + + /** + * @param $host + * @param $port + * @param $user + * @param $password + * @param $dbname + * + * @return \databox + */ + public function create($host, $port, $user, $password, $dbname) + { + $params = [ + ':host' => $host, + ':port' => $port, + ':user' => $user, + ':password' => $password, + ':dbname' => $dbname + ]; + + $query = 'SELECT sbas_id FROM sbas + WHERE host = :host AND port = :port AND `user` = :user AND pwd = :password AND dbname = :dbname'; + $statement = $this->connection->executeQuery($query, $params); + + if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) { + return $this->find((int) $row['sbas_id']); + } + + $query = 'INSERT INTO sbas (ord, host, port, dbname, sqlengine, user, pwd) + SELECT COALESCE(MAX(ord), 0) + 1 AS ord, :host AS host, :port AS port, :dbname AS dbname, + "MYSQL" AS sqlengine, :user AS user, :password AS pwd FROM sbas'; + + $stmt = $this->connection->prepare($query); + $stmt->execute($params); + + $stmt->closeCursor(); + + return $this->find((int) $this->connection->lastInsertId()); + + } } diff --git a/lib/Alchemy/Phrasea/Filesystem/ApplicationPathServiceGenerator.php b/lib/Alchemy/Phrasea/Filesystem/ApplicationPathServiceGenerator.php new file mode 100644 index 0000000000..7609a36187 --- /dev/null +++ b/lib/Alchemy/Phrasea/Filesystem/ApplicationPathServiceGenerator.php @@ -0,0 +1,38 @@ +isSetup()) + ? $app['conf']->get($key) + : null; + + if (null === $path) { + $path = $default($app); + } + + // ensure path is created + $app['filesystem']->mkdir($path); + } + + return $path; + }; + } +} diff --git a/lib/Alchemy/Phrasea/Filesystem/FilesystemService.php b/lib/Alchemy/Phrasea/Filesystem/FilesystemService.php new file mode 100644 index 0000000000..b6b34894ce --- /dev/null +++ b/lib/Alchemy/Phrasea/Filesystem/FilesystemService.php @@ -0,0 +1,208 @@ +filesystem = $filesystem; + } + + /** + * @param string $repository_path + * @return string + */ + public function directorySpread($repository_path) + { + $repository_path = \p4string::addEndSlash($repository_path); + + $timestamp = strtotime(date('Y-m-d')); + $year = date('Y', $timestamp); + $month = date('m', $timestamp); + $day = date('d', $timestamp); + + $comp = $year . DIRECTORY_SEPARATOR . $month . DIRECTORY_SEPARATOR . $day . DIRECTORY_SEPARATOR; + + $n = 0; + do { + $pathout = sprintf('%s%s%05d', $repository_path, $comp, $n++); + } while (is_dir($pathout) && iterator_count(new \DirectoryIterator($pathout)) > 100); + + $this->filesystem->mkdir($pathout, 0750); + + return $pathout . DIRECTORY_SEPARATOR; + } + + public function exists($path) + { + return $this->filesystem->exists($path); + } + + public function generateSubdefPathname(\record_adapter $record, \databox_subdef $subdef, $oldVersion) + { + if ($oldVersion) { + $pathdest = \p4string::addEndSlash(pathinfo($oldVersion, PATHINFO_DIRNAME)); + } else { + $pathdest = $this->directorySpread($subdef->get_path()); + } + + return $pathdest . $this->generateSubdefFilename($record, $subdef); + } + + /** + * @param \record_adapter $record + * @param string|\SplFileInfo $source + * @return string + */ + public function generateDocumentFilename(\record_adapter $record, $source) + { + if (!$source instanceof \SplFileInfo) { + $source = new \SplFileInfo($source); + } + + return $record->getRecordId() . '_document' . strtolower($source->getExtension()); + } + + /** + * @param \record_adapter $record + * @param \databox_subdef $subdef + * @param string $marker + * @return string + */ + public function generateSubdefFilename(\record_adapter $record, \databox_subdef $subdef, $marker = '') + { + return $record->getRecordId() . '_' . $marker . $subdef->get_name() . '.' . $this->getExtensionFromSpec($subdef->getSpecs()); + } + + public function generateSubdefSubstitutionPathname(\record_adapter $record, \databox_subdef $subdef) + { + $pathdest = $this->directorySpread($subdef->get_path()); + + return $pathdest . $this->generateSubdefFilename($record, $subdef, '0_'); + } + + /** + * @param \databox $databox + * @return string + */ + public function generateDataboxDocumentBasePath(\databox $databox) + { + $baseprefs = $databox->get_sxml_structure(); + + return $this->directorySpread(\p4string::addEndSlash((string)($baseprefs->path))); + } + + /** + * Write Media source file with given filename + * + * @param \databox $databox + * @param string $source + * @param string $filename + */ + public function writeMediaSourceFile(\databox $databox, $source, $filename) + { + $realPath = $this->generateDataboxDocumentBasePath($databox) . $filename; + + $this->filesystem->copy($source, $realPath, true); + $this->filesystem->chmod($realPath, 0760); + } + + /** + * Copy file from source to target + * + * @param string $source + * @param string $target + */ + public function copy($source, $target) + { + $this->filesystem->copy($source, $target, true); + } + + public function chmod($files, $mode, $umask = 0000, $recursive = false) + { + $this->filesystem->chmod($files, $mode, $umask, $recursive); + } + + /** + * Get the extension from MediaAlchemyst specs + * + * @param SpecificationInterface $spec + * + * @return string + */ + private function getExtensionFromSpec(SpecificationInterface $spec) + { + switch ($spec->getType()) { + case SpecificationInterface::TYPE_IMAGE: + return 'jpg'; + case SpecificationInterface::TYPE_ANIMATION: + return 'gif'; + case SpecificationInterface::TYPE_AUDIO: + return $this->getExtensionFromAudioCodec($spec->getAudioCodec()); + case SpecificationInterface::TYPE_VIDEO: + return $this->getExtensionFromVideoCodec($spec->getVideoCodec()); + case SpecificationInterface::TYPE_SWF: + return 'swf'; + } + + return null; + } + + /** + * Get the extension from audiocodec + * + * @param string $audioCodec + * + * @return string + */ + private function getExtensionFromAudioCodec($audioCodec) + { + switch ($audioCodec) { + case 'flac': + return 'flac'; + case 'libvorbis': + return 'ogg'; + case 'libmp3lame': + return 'mp3'; + } + + return null; + } + + /** + * Get the extension from videocodec + * + * @param string $videoCodec + * + * @return string + */ + private function getExtensionFromVideoCodec($videoCodec) + { + switch ($videoCodec) { + case 'libtheora': + return 'ogv'; + case 'libvpx': + return 'webm'; + case 'libx264': + return 'mp4'; + } + + return null; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Provider/TemporaryFilesystemServiceProvider.php b/lib/Alchemy/Phrasea/Filesystem/FilesystemServiceProvider.php similarity index 56% rename from lib/Alchemy/Phrasea/Core/Provider/TemporaryFilesystemServiceProvider.php rename to lib/Alchemy/Phrasea/Filesystem/FilesystemServiceProvider.php index 1dacecf881..2b181f6ef4 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/TemporaryFilesystemServiceProvider.php +++ b/lib/Alchemy/Phrasea/Filesystem/FilesystemServiceProvider.php @@ -1,6 +1,5 @@ share(function () { + return new Filesystem(); + }); + $app['temporary-filesystem.temporary-fs'] = $app->share(function (Application $app) { return new TemporaryFilesystem($app['filesystem']); }); $app['temporary-filesystem'] = $app->share(function (Application $app) { return new Manager($app['temporary-filesystem.temporary-fs'], $app['filesystem']); }); + + $app['phraseanet.filesystem'] = $app->share(function (Application $app) { + return new FilesystemService($app['filesystem']); + }); + + $app['phraseanet.lazaret_filesystem'] = $app->share(function (Application $app) { + return new LazaretFilesystemService($app['filesystem'], $app['tmp.lazaret.path'], $app['media-alchemyst']); + }); } public function boot(Application $app) { + // no-op } } diff --git a/lib/Alchemy/Phrasea/Filesystem/LazaretFilesystemService.php b/lib/Alchemy/Phrasea/Filesystem/LazaretFilesystemService.php new file mode 100644 index 0000000000..113bf6839b --- /dev/null +++ b/lib/Alchemy/Phrasea/Filesystem/LazaretFilesystemService.php @@ -0,0 +1,77 @@ +filesystem = $filesystem; + $this->alchemyst = $alchemyst; + + $this->booker = new LazaretPathBooker($filesystem, $tmpPath); + } + + /** + * Write a file in storage and mark it lazaret + * @param File $file + * @return PersistedLazaretInformation + */ + public function writeLazaret(File $file) + { + $lazaretPathname = $this->booker->bookFile($file->getOriginalName()); + $this->filesystem->copy($file->getFile()->getRealPath(), $lazaretPathname, true); + + $lazaretPathnameThumb = $this->booker->bookFile($file->getOriginalName(), 'thumb'); + try { + $this->alchemyst->turnInto($file->getFile()->getPathname(), $lazaretPathnameThumb, $this->createThumbnailSpecification()); + } catch (ExceptionInterface $e) { + // Ignore, an empty file should be present + } + + return new PersistedLazaretInformation($lazaretPathname, $lazaretPathnameThumb); + } + + /** + * @return ImageSpecification + */ + private function createThumbnailSpecification() + { + $spec = new ImageSpecification(); + + $spec->setResizeMode(ImageSpecification::RESIZE_MODE_INBOUND_FIXEDRATIO); + $spec->setDimensions(375, 275); + + return $spec; + } +} diff --git a/lib/Alchemy/Phrasea/Filesystem/LazaretPathBooker.php b/lib/Alchemy/Phrasea/Filesystem/LazaretPathBooker.php new file mode 100644 index 0000000000..824ed965cc --- /dev/null +++ b/lib/Alchemy/Phrasea/Filesystem/LazaretPathBooker.php @@ -0,0 +1,87 @@ +filesystem = $filesystem; + $this->tmpPath = $tmpPath; + + if (!is_callable($pathResolver)) { + throw new \LogicException('pathResolver should be callable'); + } + + $this->pathResolver = $pathResolver; + } + + /** + * @param string $filename + * @param string $suffix + * @return string + */ + public function bookFile($filename, $suffix = '') + { + $output = $this->tmpPath .'/lzrt_' . substr($filename, 0, 3) . '_' . $suffix . '.' . pathinfo($filename, PATHINFO_EXTENSION); + $infos = pathinfo($output); + $n = 0; + + while (true) { + $output = sprintf('%s/%s-%d%s', $infos['dirname'], $infos['filename'], ++ $n, (isset($infos['extension']) ? '.' . $infos['extension'] : '')); + + try { + if (! $this->filesystem->exists($output)) { + $this->filesystem->touch($output); + break; + } + } catch (IOException $e) { + + } + } + + return $this->resolvePath($output); + } + + /** + * @param string $path + * @return string + */ + private function resolvePath($path) + { + $callable = $this->pathResolver; + + return $callable($path); + } +} diff --git a/lib/Alchemy/Phrasea/Filesystem/PersistedLazaretInformation.php b/lib/Alchemy/Phrasea/Filesystem/PersistedLazaretInformation.php new file mode 100644 index 0000000000..e9bd0a5c45 --- /dev/null +++ b/lib/Alchemy/Phrasea/Filesystem/PersistedLazaretInformation.php @@ -0,0 +1,46 @@ +fileCopyRealPath = $fileCopyRealPath; + $this->thumbnailRealPath = $thumbnailRealPath; + } + + /** + * @return string + */ + public function getFilename() + { + return pathinfo($this->fileCopyRealPath, PATHINFO_BASENAME); + } + + /** + * @return string + */ + public function getThumbnailFilename() + { + return pathinfo($this->thumbnailRealPath, PATHINFO_BASENAME); + } +} diff --git a/lib/Alchemy/Phrasea/Helper/User/Edit.php b/lib/Alchemy/Phrasea/Helper/User/Edit.php index 95b0973e44..e8d0381dd2 100644 --- a/lib/Alchemy/Phrasea/Helper/User/Edit.php +++ b/lib/Alchemy/Phrasea/Helper/User/Edit.php @@ -227,7 +227,7 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper 'users' => $this->users, 'users_serial' => implode(';', $this->users), 'base_id' => $this->base_id, - 'collection' => \collection::get_from_base_id($this->app, $this->base_id), + 'collection' => \collection::getByBaseId($this->app, $this->base_id), ]; } @@ -331,7 +331,7 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper 'users' => $this->users, 'users_serial' => implode(';', $this->users), 'base_id' => $this->base_id, - 'collection' => \collection::get_from_base_id($this->app, $this->base_id), + 'collection' => \collection::getByBaseId($this->app, $this->base_id), ]; } @@ -391,7 +391,7 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper 'users' => $this->users, 'users_serial' => implode(';', $this->users), 'base_id' => $this->base_id, - 'collection' => \collection::get_from_base_id($this->app, $this->base_id), + 'collection' => \collection::getByBaseId($this->app, $this->base_id), ]; } diff --git a/lib/Alchemy/Phrasea/Http/BinaryFileResponse.php b/lib/Alchemy/Phrasea/Http/BinaryFileResponse.php new file mode 100644 index 0000000000..0e19e5a9c3 --- /dev/null +++ b/lib/Alchemy/Phrasea/Http/BinaryFileResponse.php @@ -0,0 +1,66 @@ +headers->has('X-Sendfile-Type')) + && $request->headers->has('Range') && $request->headers->has('If-Range') + && $this->hasValidIfRangeHeader($request->headers->get('If-Range')) + ) { + $range = $request->headers->get('Range'); + $fileSize = $this->file->getSize(); + + list($start, $end) = explode('-', substr($range, 6), 2) + array(0); + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start <= $end) { + if ($start < 0 || $end > $fileSize - 1) { + $this->setStatusCode(416); + } elseif ($start !== 0 || $end !== $fileSize - 1) { + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + $this->headers->set('Content-Length', $end - $start + 1); + } + } + + } + + return $this; + } + + private function hasValidIfRangeHeader($header) + { + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' == $header; + } +} diff --git a/lib/Alchemy/Phrasea/Http/DeliverDataInterface.php b/lib/Alchemy/Phrasea/Http/DeliverDataInterface.php index af37b05a13..f9cfaf00f3 100644 --- a/lib/Alchemy/Phrasea/Http/DeliverDataInterface.php +++ b/lib/Alchemy/Phrasea/Http/DeliverDataInterface.php @@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\Http; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; interface DeliverDataInterface @@ -26,17 +27,7 @@ interface DeliverDataInterface * @param string $disposition * @param string|null $mimeType * @param integer $cacheDuration + * @return Response */ public function deliverFile($file, $filename = null, $disposition = self::DISPOSITION_INLINE, $mimeType = null, $cacheDuration = null); - - /** - * Return a HTTP Response ready to deliver data - * - * @param string $data - * @param string $filename - * @param string $mimeType - * @param string $disposition - * @param integer $cacheDuration - */ - public function deliverData($data, $filename, $mimeType, $disposition = self::DISPOSITION_INLINE, $cacheDuration = null); } diff --git a/lib/Alchemy/Phrasea/Http/ServeFileResponseFactory.php b/lib/Alchemy/Phrasea/Http/ServeFileResponseFactory.php index 636f9e211a..639dfbd378 100644 --- a/lib/Alchemy/Phrasea/Http/ServeFileResponseFactory.php +++ b/lib/Alchemy/Phrasea/Http/ServeFileResponseFactory.php @@ -11,10 +11,6 @@ namespace Alchemy\Phrasea\Http; -use Alchemy\Phrasea\Application; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\BinaryFileResponse; - class ServeFileResponseFactory implements DeliverDataInterface { private $unicode; @@ -24,20 +20,6 @@ class ServeFileResponseFactory implements DeliverDataInterface $this->unicode = $unicode; } - /** - * @param Application $app - * @return self - */ - public static function create(Application $app) - { - return new self( - $app['unicode'] - ); - } - - /** - * {@inheritdoc} - */ public function deliverFile($file, $filename = '', $disposition = self::DISPOSITION_INLINE, $mimeType = null, $cacheDuration = null) { $response = new BinaryFileResponse($file); @@ -53,25 +35,6 @@ class ServeFileResponseFactory implements DeliverDataInterface return $response; } - /** - * {@inheritdoc} - */ - public function deliverData($data, $filename, $mimeType, $disposition = self::DISPOSITION_INLINE, $cacheDuration = null) - { - $response = new Response($data); - $response->headers->set('Content-Disposition', $response->headers->makeDisposition( - $disposition, - $this->sanitizeFilename($filename), - $this->sanitizeFilenameFallback($filename - ))); - $response->headers->set('Content-Type', $mimeType); - if (null !== $cacheDuration) { - $response->setMaxAge($cacheDuration); - } - - return $response; - } - private function sanitizeFilename($filename) { return str_replace(['/', '\\'], '', $filename); diff --git a/lib/Alchemy/Phrasea/Http/StaticFile/StaticMode.php b/lib/Alchemy/Phrasea/Http/StaticFile/StaticMode.php index de5a1b4b7d..ca357da0a5 100644 --- a/lib/Alchemy/Phrasea/Http/StaticFile/StaticMode.php +++ b/lib/Alchemy/Phrasea/Http/StaticFile/StaticMode.php @@ -11,7 +11,6 @@ namespace Alchemy\Phrasea\Http\StaticFile; -use Alchemy\Phrasea\Http\AbstractServerMode; use Alchemy\Phrasea\Http\StaticFile\Symlink\SymLinker; use Guzzle\Http\Url; @@ -26,10 +25,10 @@ class StaticMode /** * @param $pathFile - * @param null $etag + * @param null|string $etag * @return Url */ - public function getUrl($pathFile, $etag=null) + public function getUrl($pathFile, $etag = null) { $this->ensureSymlink($pathFile); diff --git a/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php b/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php index 60603fc16b..221fa68131 100644 --- a/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php +++ b/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php @@ -11,10 +11,8 @@ namespace Alchemy\Phrasea\Http\StaticFile\Symlink; -use Alchemy\Phrasea\Exception\InvalidArgumentException; use Silex\Application; use Symfony\Component\Filesystem\Filesystem; -use Guzzle\Http\Url; /** * Create & retrieve symlinks @@ -25,15 +23,6 @@ class SymLinker protected $fs; protected $symlinkDir; - public static function create(Application $app) - { - return new SymLinker( - $app['phraseanet.thumb-symlinker-encoder'], - $app['filesystem'], - $app['thumbnail.path'] - ); - } - public function __construct(SymLinkerEncoder $encoder, Filesystem $fs, $symlinkDir) { $this->encoder = $encoder; @@ -51,6 +40,11 @@ class SymLinker $this->fs->symlink($pathFile, $this->getSymlinkPath($pathFile)) ; } + public function unlink($pathFile) + { + $this->fs->remove($this->getSymlinkPath($pathFile)); + } + public function getSymlink($pathFile) { return $this->encoder->encode($pathFile); diff --git a/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinkerEncoder.php b/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinkerEncoder.php index 8139ec89eb..170032d40d 100644 --- a/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinkerEncoder.php +++ b/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinkerEncoder.php @@ -11,21 +11,10 @@ namespace Alchemy\Phrasea\Http\StaticFile\Symlink; -use Silex\Application; -use Symfony\Component\Filesystem\Filesystem; -use Guzzle\Http\Url; - class SymLinkerEncoder { protected $key; - public static function create(Application $app) - { - return new self( - $app['phraseanet.configuration']['main']['key'] - ); - } - public function __construct($key) { $this->key = $key; diff --git a/lib/Alchemy/Phrasea/Media/DatafilesResolver.php b/lib/Alchemy/Phrasea/Media/DatafilesResolver.php new file mode 100644 index 0000000000..66fa3ec490 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/DatafilesResolver.php @@ -0,0 +1,40 @@ +appbox = $appbox; + } + + public function resolve(Request $request, $routeName, array $routeParameters) + { + $parameters = array_intersect_key($routeParameters, [ + 'sbas_id' => null, + 'record_id' => null, + 'subdef' => null, + ]); + + $databox = $this->appbox->get_databox((int) $parameters['sbas_id']); + $record = $databox->get_record((int)$parameters['record_id']); + $subdef = $record->get_subdef($parameters['subdef']); + + return new MediaInformation($subdef, $request, $routeName, $routeParameters); + } +} diff --git a/lib/Alchemy/Phrasea/Media/MediaAccessorResolver.php b/lib/Alchemy/Phrasea/Media/MediaAccessorResolver.php new file mode 100644 index 0000000000..e5bcd8fff8 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/MediaAccessorResolver.php @@ -0,0 +1,44 @@ +appbox = $appbox; + $this->controller = $controller; + } + + public function resolve(Request $request, $routeName, array $routeParameters) + { + $parameters = array_intersect_key($routeParameters, [ + 'token' => null, + ]); + + list ($sbas_id, $record_id, $subdefName) = $this->controller->validateToken($parameters['token']); + + $databox = $this->appbox->get_databox($sbas_id); + $record = $databox->get_record($record_id); + $subdef = $record->get_subdef($subdefName); + + return new MediaInformation($subdef, $request, $routeName, $routeParameters); + } +} diff --git a/lib/Alchemy/Phrasea/Media/PermalinkMediaResolver.php b/lib/Alchemy/Phrasea/Media/PermalinkMediaResolver.php new file mode 100644 index 0000000000..5854987085 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/PermalinkMediaResolver.php @@ -0,0 +1,44 @@ +appbox = $appbox; + } + + public function resolve(Request $request, $routeName, array $routeParameters) + { + $parameters = array_replace( + $request->query->all(), + array_intersect_key($routeParameters, [ + 'sbas_id' => null, + 'record_id' => null, + 'subdef' => null, + 'label' => null, + ]) + ); + + $databox = $this->appbox->get_databox((int) $parameters['sbas_id']); + $record = $databox->get_record((int)$parameters['record_id']); + $subdef = $record->get_subdef($parameters['subdef']); + + return new MediaInformation($subdef, $request, $routeName, $routeParameters); + } +} diff --git a/lib/Alchemy/Phrasea/Media/Subdef/Subdef.php b/lib/Alchemy/Phrasea/Media/Subdef/Subdef.php index 96e6632449..db42b01c84 100644 --- a/lib/Alchemy/Phrasea/Media/Subdef/Subdef.php +++ b/lib/Alchemy/Phrasea/Media/Subdef/Subdef.php @@ -11,6 +11,8 @@ namespace Alchemy\Phrasea\Media\Subdef; +use MediaAlchemyst\Specification\SpecificationInterface; + interface Subdef { const TYPE_IMAGE = 'image'; @@ -19,9 +21,20 @@ interface Subdef const TYPE_AUDIO = 'audio'; const TYPE_FLEXPAPER = 'flexpaper'; + /** + * One of Subdef Type const + * + * @return string + */ public function getType(); + /** + * @return string + */ public function getDescription(); + /** + * @return SpecificationInterface + */ public function getMediaAlchemystSpec(); } diff --git a/lib/Alchemy/Phrasea/Media/SubdefGenerator.php b/lib/Alchemy/Phrasea/Media/SubdefGenerator.php index 27e1cd5b09..3168417c49 100644 --- a/lib/Alchemy/Phrasea/Media/SubdefGenerator.php +++ b/lib/Alchemy/Phrasea/Media/SubdefGenerator.php @@ -1,5 +1,4 @@ app = $app; $this->alchemyst = $alchemyst; @@ -43,17 +46,10 @@ class SubdefGenerator $this->mediavorus = $mediavorus; } - private function dispatch($eventName, RecordEvent $event) - { - $this->app['dispatcher']->dispatch($eventName, $event); - } - public function generateSubdefs(\record_adapter $record, array $wanted_subdefs = null) { if (null === $subdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType())) { $this->logger->info(sprintf('Nothing to do for %s', $record->getType())); - - return $this; } $this->dispatch( @@ -75,13 +71,13 @@ class SubdefGenerator $pathdest = null; if ($record->has_subdef($subdefname) && $record->get_subdef($subdefname)->is_physically_present()) { - $pathdest = $record->get_subdef($subdefname)->get_pathfile(); + $pathdest = $record->get_subdef($subdefname)->getRealPath(); $record->get_subdef($subdefname)->remove_file(); $this->logger->info(sprintf('Removed old file for %s', $subdefname)); $record->clearSubdefCache($subdefname); } - $pathdest = $this->generateSubdefPathname($record, $subdef, $pathdest); + $pathdest = $this->filesystem->generateSubdefPathname($record, $subdef, $pathdest); $this->dispatch( RecordEvents::SUB_DEFINITION_CREATION, @@ -91,7 +87,7 @@ class SubdefGenerator ) ); - $this->logger->addInfo(sprintf('Generating subdef %s to %s', $subdefname, $pathdest)); + $this->logger->info(sprintf('Generating subdef %s to %s', $subdefname, $pathdest)); $this->generateSubdef($record, $subdef, $pathdest); if ($this->filesystem->exists($pathdest)) { @@ -128,15 +124,13 @@ class SubdefGenerator $mediaCreated ) ); - - return $this; } private function generateSubdef(\record_adapter $record, \databox_subdef $subdef_class, $pathdest) { try { if (null === $record->get_hd_file()) { - $this->logger->addInfo('No HD file found, aborting'); + $this->logger->info('No HD file found, aborting'); return; } @@ -146,99 +140,4 @@ class SubdefGenerator $this->logger->error(sprintf('Subdef generation failed for record %d with message %s', $record->getRecordId(), $e->getMessage())); } } - - private function generateSubdefPathname(\record_adapter $record, \databox_subdef $subdef, $oldVersion = null) - { - if ($oldVersion) { - $pathdest = \p4string::addEndSlash(pathinfo($oldVersion, PATHINFO_DIRNAME)); - } else { - $pathdest = \databox::dispatch($this->filesystem, $subdef->get_path()); - } - - return $pathdest . $record->getRecordId() . '_' . $subdef->get_name() . '.' . $this->getExtensionFromSpec($subdef->getSpecs()); - } - - /** - * Get the extension from MediaAlchemyst specs - * - * @param SpecificationInterface $spec - * - * @return string - */ - private function getExtensionFromSpec(SpecificationInterface $spec) - { - $extension = null; - - switch (true) { - case $spec->getType() === SpecificationInterface::TYPE_IMAGE: - $extension = 'jpg'; - break; - case $spec->getType() === SpecificationInterface::TYPE_ANIMATION: - $extension = 'gif'; - break; - case $spec->getType() === SpecificationInterface::TYPE_AUDIO: - $extension = $this->getExtensionFromAudioCodec($spec->getAudioCodec()); - break; - case $spec->getType() === SpecificationInterface::TYPE_VIDEO: - $extension = $this->getExtensionFromVideoCodec($spec->getVideoCodec()); - break; - case $spec->getType() === SpecificationInterface::TYPE_SWF: - $extension = 'swf'; - break; - } - - return $extension; - } - - /** - * Get the extension from audiocodec - * - * @param string $audioCodec - * - * @return string - */ - private function getExtensionFromAudioCodec($audioCodec) - { - $extension = null; - - switch ($audioCodec) { - case 'flac': - $extension = 'flac'; - break; - case 'libvorbis': - $extension = 'ogg'; - break; - case 'libmp3lame': - $extension = 'mp3'; - break; - } - - return $extension; - } - - /** - * Get the extension from videocodec - * - * @param string $videoCodec - * - * @return string - */ - private function getExtensionFromVideoCodec($videoCodec) - { - $extension = null; - - switch ($videoCodec) { - case 'libtheora': - $extension = 'ogv'; - break; - case 'libvpx': - $extension = 'webm'; - break; - case 'libx264': - $extension = 'mp4'; - break; - } - - return $extension; - } } diff --git a/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php b/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php index 0eb43f4eb7..e16045f8f1 100644 --- a/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php +++ b/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php @@ -1,9 +1,8 @@ alchemyst = $alchemyst; $this->app = $app; @@ -36,67 +35,117 @@ class SubdefSubstituer $this->dispatcher = $dispatcher; } - public function substitute(\record_adapter $record, $name, MediaInterface $media) + /** + * @param \record_adapter $record + * @param string $name + * @param MediaInterface $media + * @param bool $adapt + * + * @deprecated use {@link self::substituteDocument} or {@link self::substituteSubdef} instead + */ + public function substitute(\record_adapter $record, $name, MediaInterface $media, $adapt = true) { - $newfilename = $record->get_record_id() . '_0_' . $name . '.' . $media->getFile()->getExtension(); - - $subdef_def = false; - if ($name == 'document') { - $baseprefs = $record->get_databox()->get_sxml_structure(); - $pathhd = \p4string::addEndSlash((string) ($baseprefs->path)); + $this->substituteDocument($record, $media); - $filehd = $record->get_record_id() . "_document." . strtolower($media->getFile()->getExtension()); - $pathhd = \databox::dispatch($this->fs, $pathhd); + return; + } - $this->fs->copy($media->getFile()->getRealPath(), $pathhd . $filehd, true); + $this->substituteSubdef($record, $name, $media, $adapt); + } - $subdefFile = $pathhd . $filehd; - $meta_writable = true; + public function substituteDocument(\record_adapter $record, MediaInterface $media) + { + /** @var \SplFileInfo $file */ + $file = $media->getFile(); + + $source = $file->getRealPath(); + $target = $this->fs->generateDocumentFilename($record, $file); + + $this->fs->writeMediaSourceFile($record->getDatabox(), $source, $target); + + $media = $this->mediavorus->guess($source); + + $this->createMediaSubdef($record, 'document', $media); + + $record->write_metas(); + + $record->rebuild_subdefs(); + + $this->dispatcher->dispatch(RecordEvents::MEDIA_SUBSTITUTED, new MediaSubstitutedEvent($record)); + } + + /** + * @param \record_adapter $record + * @param string $name + * @param MediaInterface $media + * @param bool $adapt + */ + public function substituteSubdef(\record_adapter $record, $name, MediaInterface $media, $adapt = true) + { + if ($name == 'document') { + throw new \RuntimeException('Cannot substitute documents, only subdefs allowed'); + } + + $type = $record->isStory() ? 'image' : $record->getType(); + $databox_subdef = $record->getDatabox()->get_subdef_structure()->get_subdef($type, $name); + + if ($this->isOldSubdefPresent($record, $name)) { + $path_file_dest = $record->get_subdef($name)->getRealPath(); + $record->get_subdef($name)->remove_file(); + $record->clearSubdefCache($name); } else { - $type = $record->isStory() ? 'image' : $record->get_type(); - $subdef_def = $record->get_databox()->get_subdef_structure()->get_subdef($type, $name); - - if ($record->has_subdef($name) && $record->get_subdef($name)->is_physically_present()) { - $path_file_dest = $record->get_subdef($name)->get_pathfile(); - $record->get_subdef($name)->remove_file(); - $record->clearSubdefCache($name); - } else { - $path = \databox::dispatch($this->fs, $subdef_def->get_path()); - $this->fs->mkdir($path, 0750); - $path_file_dest = $path . $newfilename; - } + $path_file_dest = $this->fs->generateSubdefSubstitutionPathname($record, $databox_subdef); + } + if($adapt) { try { $this->alchemyst->turnInto( $media->getFile()->getRealPath(), $path_file_dest, - $subdef_def->getSpecs() + $databox_subdef->getSpecs() ); } catch (MediaAlchemystException $e) { return; } - - $subdefFile = $path_file_dest; - - $meta_writable = $subdef_def->meta_writeable(); + } else { + $this->fs->copy($media->getFile()->getRealPath(), $path_file_dest); } - $this->fs->chmod($subdefFile, 0760); - $media = $this->mediavorus->guess($subdefFile); + $this->fs->chmod($path_file_dest, 0760); + $media = $this->mediavorus->guess($path_file_dest); - \media_subdef::create($this->app, $record, $name, $media); + $this->createMediaSubdef($record, $name, $media); - $record->delete_data_from_cache(\record_adapter::CACHE_SUBDEFS); - - if ($meta_writable) { + if ($databox_subdef->isMetadataUpdateRequired()) { $record->write_metas(); } - if ($name == 'document') { - $record->rebuild_subdefs(); - } + $record->rebuild_subdefs(); $this->dispatcher->dispatch(RecordEvents::MEDIA_SUBSTITUTED, new MediaSubstitutedEvent($record)); } + + /** + * @param \record_adapter $record + * @param string $name + * @return bool + */ + private function isOldSubdefPresent(\record_adapter $record, $name) + { + return $record->has_subdef($name) && $record->get_subdef($name)->is_physically_present(); + } + + /** + * @param \record_adapter $record + * @param string $name + * @param MediaInterface $media + */ + private function createMediaSubdef(\record_adapter $record, $name, MediaInterface $media) + { + $subdef = \media_subdef::create($this->app, $record, $name, $media); + $subdef->set_substituted(true); + + $record->delete_data_from_cache(\record_adapter::CACHE_SUBDEFS); + } } diff --git a/lib/Alchemy/Phrasea/Model/Entities/Feed.php b/lib/Alchemy/Phrasea/Model/Entities/Feed.php index 6f1c5a8a93..55a2156128 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/Feed.php +++ b/lib/Alchemy/Phrasea/Model/Entities/Feed.php @@ -302,7 +302,7 @@ class Feed implements FeedInterface public function getCollection(Application $app) { if ($this->getBaseId() !== null) { - return \collection::get_from_base_id($app, $this->getBaseId()); + return \collection::getByBaseId($app, $this->getBaseId()); } } diff --git a/lib/Alchemy/Phrasea/Model/Entities/LazaretFile.php b/lib/Alchemy/Phrasea/Model/Entities/LazaretFile.php index 1eabdf1fd7..c9a43ec845 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/LazaretFile.php +++ b/lib/Alchemy/Phrasea/Model/Entities/LazaretFile.php @@ -211,7 +211,7 @@ class LazaretFile */ public function getCollection(Application $app) { - return \collection::get_from_base_id($app, $this->getBaseId()); + return \collection::getByBaseId($app, $this->getBaseId()); } /** diff --git a/lib/Alchemy/Phrasea/Model/Entities/Registration.php b/lib/Alchemy/Phrasea/Model/Entities/Registration.php index e00b82455f..6d7e08052f 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/Registration.php +++ b/lib/Alchemy/Phrasea/Model/Entities/Registration.php @@ -141,7 +141,7 @@ class Registration */ public function getCollection(Application $app) { - return \collection::get_from_base_id($app, $this->baseId); + return \collection::getByBaseId($app, $this->baseId); } /** diff --git a/lib/Alchemy/Phrasea/Model/Entities/Session.php b/lib/Alchemy/Phrasea/Model/Entities/Session.php index f6a6813506..6653599b68 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/Session.php +++ b/lib/Alchemy/Phrasea/Model/Entities/Session.php @@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; /** - * @ORM\Table(name="Sessions", indexes={@ORM\index(name="session_user_id", columns={"user_id"})}) + * @ORM\Table(name="Sessions") * @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\SessionRepository") */ class Session diff --git a/lib/Alchemy/Phrasea/Model/Entities/WebhookEvent.php b/lib/Alchemy/Phrasea/Model/Entities/WebhookEvent.php index d8c5f959b3..3017239acf 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/WebhookEvent.php +++ b/lib/Alchemy/Phrasea/Model/Entities/WebhookEvent.php @@ -21,6 +21,9 @@ class WebhookEvent const USER_DELETED = 'user.deleted'; const USER_DELETED_TYPE = 'user.deleted'; + const RECORD_SUBDEF_CREATED = 'record.subdef.created'; + const RECORD_SUBDEF_TYPE = 'record.subdef'; + /** * @ORM\Column(type="integer") * @ORM\Id @@ -58,25 +61,6 @@ class WebhookEvent */ private $created; - public static function types() - { - return [ - self::FEED_ENTRY_TYPE, - self::USER_REGISTRATION_TYPE, - self::USER_DELETED_TYPE - ]; - } - - public static function events() - { - return [ - self::NEW_FEED_ENTRY, - self::USER_REGISTRATION_REJECTED, - self::USER_REGISTRATION_GRANTED, - self::USER_DELETED - ]; - } - /** * @param \DateTime $created * @@ -126,17 +110,12 @@ class WebhookEvent } /** - * @param $name + * @param string $name * - * @return WebhookEvent - * @throws \InvalidArgumentException + * @return $this */ public function setName($name) { - if (!in_array($name, self::events())) { - throw new \InvalidArgumentException("Invalid event name"); - } - $this->name = $name; return $this; @@ -179,16 +158,12 @@ class WebhookEvent } /** - * @param $type + * @param string $type * * @return $this - * @throws \InvalidArgumentException */ public function setType($type) { - if (!in_array($type, self::types())) { - throw new \InvalidArgumentException("Invalid event name"); - } $this->type = $type; return $this; diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/UserManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/UserManipulator.php index e8a5fc8128..adfebb6f4f 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/UserManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/UserManipulator.php @@ -347,9 +347,9 @@ class UserManipulator implements ManipulatorInterface /** * Makes given variable traversable. * - * @param mixed $var + * @param User|User[] $var * - * @return array + * @return array|\Traversable|User[] */ private function makeTraversable($var) { diff --git a/lib/Alchemy/Phrasea/Model/Repositories/BasketRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/BasketRepository.php index 6f081d98b3..5db12de16e 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/BasketRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/BasketRepository.php @@ -1,9 +1,8 @@ translator = $translator; + } + + /** + * @param string $id + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + private function trans($id, $parameters = [], $domain = null, $locale = null) + { + if ($this->translator) { + return $this->translator->trans($id, $parameters, $domain, $locale); + } + + return $id; + } + /** * Returns all basket for a given user that are not marked as archived * @@ -135,11 +161,11 @@ class BasketRepository extends EntityRepository $basket = $query->getOneOrNullResult(); - /* @var $basket Basket */ if (null === $basket) { - throw new NotFoundHttpException(_('Basket is not found')); + throw new NotFoundHttpException($this->trans('Basket is not found')); } + /* @var Basket $basket */ if ($basket->getUser()->getId() != $user->getId()) { $participant = false; @@ -152,7 +178,7 @@ class BasketRepository extends EntityRepository } } if (!$participant) { - throw new AccessDeniedHttpException(_('You have not access to this basket')); + throw new AccessDeniedHttpException($this->trans('You have not access to this basket')); } } diff --git a/lib/Alchemy/Phrasea/Model/Repositories/UserRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/UserRepository.php index 7e14289f03..9d66c73285 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/UserRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/UserRepository.php @@ -25,7 +25,7 @@ class UserRepository extends EntityRepository /** * Finds admins. * - * @return array + * @return User[] */ public function findAdmins() { diff --git a/lib/Alchemy/Phrasea/Out/Module/PDF.php b/lib/Alchemy/Phrasea/Out/Module/PDF.php index a7ef976057..8fe925d8b3 100644 --- a/lib/Alchemy/Phrasea/Out/Module/PDF.php +++ b/lib/Alchemy/Phrasea/Out/Module/PDF.php @@ -146,7 +146,7 @@ class PDF $irow = $ipage = 0; $icol = -1; foreach ($this->records as $rec) { - /* @var $rec record_adapter */ + /* @var \record_adapter $rec */ if (++$icol >= $NDiapoW) { $icol = 0; if (++$irow >= $NDiapoH) { @@ -164,7 +164,7 @@ class PDF $subdef = $rec->get_thumbnail(); } - $fimg = $subdef->get_pathfile(); + $fimg = $subdef->getRealPath(); if (!$this->app->getAclForUser($this->app->getAuthenticatedUser())->has_right_on_base($rec->get_base_id(), "nowatermark") && $subdef->get_type() == \media_subdef::TYPE_IMAGE) { @@ -226,10 +226,10 @@ class PDF $ndoc = 0; foreach ($this->records as $rec) { - /* @var $rec record_adapter */ + /* @var \record_adapter $rec */ $subdef = $rec->get_subdef('thumbnail'); - $fimg = $subdef->get_pathfile(); + $fimg = $subdef->getRealPath(); $wimg = $himg = 50; // 1px = 3.77952 mm @@ -322,7 +322,7 @@ class PDF } foreach ($this->records as $krec => $rec) { - /* @var $rec record_adapter */ + /* @var \record_adapter $rec */ $this->pdf->AddPage(); @@ -350,7 +350,7 @@ class PDF } } - $collection = \collection::get_from_base_id($this->app, $rec->get_base_id()); + $collection = \collection::getByBaseId($this->app, $rec->get_base_id()); $vn = ""; if (false !== $str = simplexml_load_string($collection->get_prefs())) { @@ -436,7 +436,7 @@ class PDF $subdef = $rec->get_thumbnail(); } - $f = $subdef->get_pathfile(); + $f = $subdef->getRealPath(); if (!$this->app->getAclForUser($this->app->getAuthenticatedUser())->has_right_on_base($rec->get_base_id(), "nowatermark") && $subdef->get_type() == \media_subdef::TYPE_IMAGE) diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php index 36815ac3ec..9c5f77f9f6 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php @@ -4,6 +4,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Search; use Alchemy\Phrasea\SearchEngine\Elastic\AST; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception; +use Alchemy\Phrasea\SearchEngine\Elastic\Mapping; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; use Hoa\Compiler\Llk\TreeNode; use Hoa\Visitor\Element; @@ -207,7 +208,7 @@ class QueryVisitor implements Visit if ($key instanceof AST\KeyValue\TimestampKey) { return true; } elseif ($key instanceof AST\KeyValue\FieldKey) { - return $this->structure->get($key->getName()) !== null; + return $this->structure->typeOf($key->getName()) === Mapping::TYPE_DATE; } return false; } diff --git a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php index 6bd3bcf00f..0b1dad1798 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php @@ -481,7 +481,7 @@ class SearchEngineOptions break; case in_array($key, ['collections', 'business_fields']): $value = array_map(function ($base_id) use ($app) { - return \collection::get_from_base_id($app, $base_id); + return \collection::getByBaseId($app, $base_id); }, $value); break; } @@ -572,7 +572,7 @@ class SearchEngineOptions $bas = []; foreach ($selected_bases as $bas_id) { try { - $bas[$bas_id] = \collection::get_from_base_id($app, $bas_id); + $bas[$bas_id] = \collection::getByBaseId($app, $bas_id); } catch (\Exception_Databox_CollectionNotFound $e) { // Ignore } diff --git a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/SessionMigration.php b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/SessionMigration.php index 8993dcb4eb..c9760728c7 100644 --- a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/SessionMigration.php +++ b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/SessionMigration.php @@ -17,15 +17,17 @@ class SessionMigration extends AbstractMigration { public function isAlreadyApplied() { - return $this->tableExists('Sessions'); + return false; } public function doUpSql(Schema $schema) { - $this->addSql("CREATE TABLE SessionModules (id INT AUTO_INCREMENT NOT NULL, session_id INT DEFAULT NULL, module_id INT NOT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_BA36EF49613FECDF (session_id), UNIQUE INDEX unique_module (session_id, module_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); - $this->addSql("CREATE TABLE Sessions (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, user_agent VARCHAR(512) NOT NULL, ip_address VARCHAR(40) DEFAULT NULL, platform VARCHAR(128) DEFAULT NULL, browser_name VARCHAR(128) DEFAULT NULL, browser_version VARCHAR(32) DEFAULT NULL, screen_width INT DEFAULT NULL, screen_height INT DEFAULT NULL, token VARCHAR(128) DEFAULT NULL, nonce VARCHAR(16) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, UNIQUE INDEX UNIQ_6316FF455F37A13B (token), INDEX IDX_6316FF45A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); - $this->addSql("ALTER TABLE SessionModules ADD CONSTRAINT FK_BA36EF49613FECDF FOREIGN KEY (session_id) REFERENCES Sessions (id)"); - $this->addSql("ALTER TABLE Sessions ADD CONSTRAINT FK_6316FF45A76ED395 FOREIGN KEY (user_id) REFERENCES Users (id)"); + if (! $schema->hasTable('Sessions')) { + $this->addSql("CREATE TABLE Sessions (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, user_agent VARCHAR(512) NOT NULL, ip_address VARCHAR(40) DEFAULT NULL, platform VARCHAR(128) DEFAULT NULL, browser_name VARCHAR(128) DEFAULT NULL, browser_version VARCHAR(32) DEFAULT NULL, screen_width INT DEFAULT NULL, screen_height INT DEFAULT NULL, token VARCHAR(128) DEFAULT NULL, nonce VARCHAR(16) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, UNIQUE INDEX UNIQ_6316FF455F37A13B (token), INDEX IDX_6316FF45A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE SessionModules (id INT AUTO_INCREMENT NOT NULL, session_id INT DEFAULT NULL, module_id INT NOT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_BA36EF49613FECDF (session_id), UNIQUE INDEX unique_module (session_id, module_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("ALTER TABLE SessionModules ADD CONSTRAINT FK_BA36EF49613FECDF FOREIGN KEY (session_id) REFERENCES Sessions (id)"); + $this->addSql("ALTER TABLE Sessions ADD CONSTRAINT FK_6316FF45A76ED395 FOREIGN KEY (user_id) REFERENCES Users (id)"); + } } public function doDownSql(Schema $schema) diff --git a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/Version20150519173347.php b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/Version20150519173347.php index 3cfe5f9212..f50aab94f6 100644 --- a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/Version20150519173347.php +++ b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/Version20150519173347.php @@ -14,6 +14,8 @@ class Version20150519173347 extends BaseMigration { $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + $this->skipIf($schema->hasTable('Secrets'), 'Table already exists'); + $this->addSql('CREATE TABLE Secrets (id INT AUTO_INCREMENT NOT NULL, creator_id INT NOT NULL, created DATETIME NOT NULL, token VARCHAR(40) COLLATE utf8_bin NOT NULL, INDEX IDX_48F428861220EA6 (creator_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB'); $this->addSql('ALTER TABLE Secrets ADD CONSTRAINT FK_48F428861220EA6 FOREIGN KEY (creator_id) REFERENCES Users (id)'); } diff --git a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/Version20151209101524.php b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/Version20151209101524.php index d080457172..c7d0766698 100644 --- a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/Version20151209101524.php +++ b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/Version20151209101524.php @@ -2,13 +2,13 @@ namespace Alchemy\Phrasea\Setup\DoctrineMigrations; -use Doctrine\DBAL\Migrations\AbstractMigration; +use Doctrine\DBAL\Migrations\AbstractMigration as BaseMigration; use Doctrine\DBAL\Schema\Schema; /** * Auto-generated Migration: Please modify to your needs! */ -class Version20151209101524 extends AbstractMigration +class Version20151209101524 extends BaseMigration { /** * @param Schema $schema diff --git a/lib/Alchemy/Phrasea/Setup/Version/PreSchemaUpgrade/Upgrade39Sessions.php b/lib/Alchemy/Phrasea/Setup/Version/PreSchemaUpgrade/Upgrade39Sessions.php new file mode 100644 index 0000000000..79d57d9d38 --- /dev/null +++ b/lib/Alchemy/Phrasea/Setup/Version/PreSchemaUpgrade/Upgrade39Sessions.php @@ -0,0 +1,72 @@ +tableExists($em, 'Sessions')) { + $this->dropTable($em, 'SessionModules'); + $this->dropTable($em, 'Sessions'); + } + } + + /** + * {@inheritdoc} + */ + public function isApplyable(Application $app) + { + return $this->tableExists($app['orm.em'], 'Sessions'); + } + + /** + * {@inheritdoc} + */ + public function rollback(EntityManager $em, \appbox $appbox, Configuration $conf) + { + + } + + /** + * Checks whether the table exists or not. + * + * @param EntityManager $em + * @param string $table + * + * @return boolean + */ + private function tableExists(EntityManager $em, $table) + { + return (Boolean) $em->createNativeQuery( + 'SHOW TABLE STATUS WHERE Name="'.$table.'" COLLATE utf8_bin ', (new ResultSetMapping())->addScalarResult('Name', 'Name') + )->getOneOrNullResult(); + } + + /** + * @param EntityManager $em + * @param $tableName + */ + private function dropTable(EntityManager $em, $tableName) + { + $em->getConnection()->getSchemaManager()->dropTable($tableName); + } +} diff --git a/lib/Alchemy/Phrasea/Setup/Version/PreSchemaUpgrade/Upgrade39Users.php b/lib/Alchemy/Phrasea/Setup/Version/PreSchemaUpgrade/Upgrade39Users.php index 1ffde761ab..f3b58fcb96 100644 --- a/lib/Alchemy/Phrasea/Setup/Version/PreSchemaUpgrade/Upgrade39Users.php +++ b/lib/Alchemy/Phrasea/Setup/Version/PreSchemaUpgrade/Upgrade39Users.php @@ -297,15 +297,19 @@ class Upgrade39Users implements PreSchemaUpgradeInterface $meta = $em->getClassMetadata('Alchemy\Phrasea\Model\Entities\User'); $connection = $em->getConnection(); $dbPlatform = $connection->getDatabasePlatform(); - $connection->beginTransaction(); - try { - $connection->query('SET FOREIGN_KEY_CHECKS=0'); - $connection->executeUpdate($dbPlatform->getTruncateTableSql($meta->getTableName())); - $connection->query('SET FOREIGN_KEY_CHECKS=1'); - $connection->commit(); - } catch (\Exception $e) { - $connection->rollback(); - throw $e; + + if ($connection->getSchemaManager()->tablesExist([ $meta->getTableName() ])) { + $connection->beginTransaction(); + + try { + $connection->query('SET FOREIGN_KEY_CHECKS=0'); + $connection->executeUpdate($dbPlatform->getTruncateTableSql($meta->getTableName())); + $connection->query('SET FOREIGN_KEY_CHECKS=1'); + $connection->commit(); + } catch (\Exception $e) { + $connection->rollback(); + throw $e; + } } } diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php index ce80c5f814..8372a6f3b1 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php @@ -868,7 +868,7 @@ class ArchiveJob extends AbstractJob } try { - $collection = \collection::get_from_coll_id($app, $databox, (int) $cid); + $collection = \collection::getByCollectionId($app, $databox, (int) $cid); if ($captionFileName === null) { $story = $this->createStory($app, $collection, $path . '/' . $representationFileName, null, $stat0, $stat1); } else { @@ -1203,7 +1203,7 @@ class ArchiveJob extends AbstractJob } try { - $collection = \collection::get_from_coll_id($app, $databox, (int) $cid); + $collection = \collection::getByCollectionId($app, $databox, (int) $cid); if ($captionFileName === null) { $this->createRecord($app, $collection, $path . '/' . $file, null, $grp_rid, null, $stat0, $stat1); diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/EmptyCollectionJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/EmptyCollectionJob.php index a3756c3161..3e361ba549 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/EmptyCollectionJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/EmptyCollectionJob.php @@ -61,7 +61,7 @@ class EmptyCollectionJob extends AbstractJob $baseId = (string) $settings->base_id; - $collection = \collection::get_from_base_id($app, $baseId); + $collection = \collection::getByBaseId($app, $baseId); $collection->empty_collection(200); if (0 === $collection->get_record_amount()) { diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/FtpJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/FtpJob.php index 52ec5fc75c..a5eabfc08f 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/FtpJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/FtpJob.php @@ -212,13 +212,13 @@ class FtpJob extends AbstractJob throw new \Exception('Impossible de creer un fichier temporaire'); } } else { - $sd = $record->get_subdefs(); - - if (!$sd || !isset($sd[$subdef])) { + try { + $sd = $record->get_subdef($subdef); + } catch (\Exception_Media_SubdefNotFound $notFount) { continue; } - $localfile = $sd[$subdef]->get_pathfile(); + $localfile = $sd->getRealPath(); if (!file_exists($localfile)) { throw new \Exception('Le fichier local n\'existe pas'); } diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/RecordMoverJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/RecordMoverJob.php index 71922028ba..8ac3f3064a 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/RecordMoverJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/RecordMoverJob.php @@ -82,7 +82,7 @@ class RecordMoverJob extends AbstractJob case 'UPDATE': // change collection ? if (array_key_exists('coll', $row)) { - $coll = \collection::get_from_coll_id($app, $databox, $row['coll']); + $coll = \collection::getByCollectionId($app, $databox, $row['coll']); $rec->move_to_collection($coll, $app['phraseanet.appbox']); if ($logsql) { $this->log('debug', sprintf("on sbas %s move rid %s to coll %s \n", $row['sbas_id'], $row['record_id'], $coll->get_coll_id())); diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/SubdefsJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/SubdefsJob.php index 01c66d6b14..22303c9111 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/SubdefsJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/SubdefsJob.php @@ -1,9 +1,8 @@ translator->trans('task::subdef:creation des sous definitions'); } - /** - * {@inheritdoc} - */ public function getJobId() { return 'Subdefs'; } - /** - * {@inheritdoc} - */ public function getDescription() { return $this->translator->trans("task::subdef:creation des sous definitions des documents d'origine"); } - /** - * {@inheritdoc} - */ public function getEditor() { return new SubdefsEditor($this->translator); } - /** - * {@inheritdoc} - */ protected function doJob(JobData $data) { $app = $data->getApplication(); $settings = simplexml_load_string($data->getTask()->getSettings()); - $thumbnailExtraction = (Boolean) (string) $settings->embedded; + $thumbnailExtraction = (bool) (string) $settings->embedded; Image2Image::$lookForEmbeddedPreview = $thumbnailExtraction; - $sqlqmark = array(); - $sqlparms = array(); - foreach(array('image', - 'video', - 'audio', - 'document', - 'flash', - 'unknown') as $type) { + $documentTypes = [ + 'image', + 'video', + 'audio', + 'document', + 'flash', + 'unknown' + ]; + + $sqlParameters = []; + + foreach($documentTypes as $type) { if (!isset($settings->{"type_" . $type}) || !\p4field::isno($settings->{"type_" . $type})) { - $sqlqmark[] = '?'; - $sqlparms[] = $type; + $sqlParameters[] = $type; } } - if(count($sqlqmark) == 0) { + + if(empty($sqlParameters)) { return; } @@ -90,16 +80,22 @@ class SubdefsJob extends AbstractJob $conn = $databox->get_connection(); $sql = 'SELECT coll_id, record_id FROM record' - . ' WHERE jeton & ' . PhraseaTokens::MAKE_SUBDEF . ' > 0' - . ' AND type IN(' . implode(',', $sqlqmark) . ')' + . ' WHERE jeton & :token > 0 AND type IN(:types)' . ' ORDER BY record_id DESC LIMIT 0, 30'; - $stmt = $conn->prepare($sql); - $stmt->execute($sqlparms); - $rs = $stmt->fetchAll(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); + $resultSet = $conn->fetchAll( + $sql, + [ + 'token' => PhraseaTokens::MAKE_SUBDEF, + 'types' => $sqlParameters, + ], + [ + 'token' => \PDO::PARAM_INT, + 'types' => Connection::PARAM_STR_ARRAY, + ] + ); $i = 0; - foreach ($rs as $row) { + foreach ($resultSet as $row) { if (!$this->isStarted()) { break; } @@ -107,9 +103,7 @@ class SubdefsJob extends AbstractJob try { $record = $databox->get_record($row['record_id']); - /** @var SubdefGenerator $sg */ - $sg = $app['subdef.generator']; - $sg->generateSubdefs($record); + $this->getSubdefGenerator($app)->generateSubdefs($record); } catch (\Exception $e) { $this->log('warning', sprintf("Generate subdefs failed for : sbasid=%s / databox=%s / recordid=%s : %s", $databox->get_sbas_id(), $databox->get_dbname() , $row['record_id'], $e->getMessage())); } @@ -119,24 +113,35 @@ class SubdefsJob extends AbstractJob . ' SET jeton=(jeton & ~(:flag_and)) | :flag_or, moddate=NOW()' . ' WHERE record_id=:record_id'; - $stmt = $conn->prepare($sql); - $stmt->execute([ + $conn->executeUpdate($sql, [ ':record_id' => $row['record_id'], ':flag_and' => PhraseaTokens::MAKE_SUBDEF, ':flag_or' => (PhraseaTokens::WRITE_META_SUBDEF | PhraseaTokens::TO_INDEX) ]); - $stmt->closeCursor(); unset($record); $i++; if ($i % 5 === 0) { - $app['elasticsearch.indexer']->flushQueue(); + $this->flushIndexerQueue($app); } } } - $app['elasticsearch.indexer']->flushQueue(); + $this->flushIndexerQueue($app); } + /** + * @param Application $app + * @return SubdefGenerator + */ + private function getSubdefGenerator(Application $app) + { + return $app['subdef.generator']; + } + + private function flushIndexerQueue(Application $app) + { + $app['elasticsearch.indexer']->flushQueue(); + } } diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/WebhookJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/WebhookJob.php index 38ac65d1e7..8616477469 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/WebhookJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/WebhookJob.php @@ -121,7 +121,8 @@ class WebhookJob extends AbstractJob ) )); - $eventFactory = new EventProcessorFactory($app); + /** @var EventProcessorFactory $eventFactory */ + $eventFactory = $app['webhook.processor_factory']; foreach ($app['repo.webhook-event']->findUnprocessedEvents() as $event) { // set event as processed diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/WriteMetadataJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/WriteMetadataJob.php index 2165f2c5ea..1fab12b7a7 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/WriteMetadataJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/WriteMetadataJob.php @@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\TaskManager\Job; +use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Core\PhraseaTokens; use Alchemy\Phrasea\Metadata\TagFactory; use Alchemy\Phrasea\TaskManager\Editor\WriteMetadataEditor; @@ -20,6 +21,7 @@ use PHPExiftool\Driver\Tag; use PHPExiftool\Exception\ExceptionInterface as PHPExiftoolException; use PHPExiftool\Writer as ExifWriter; use PHPExiftool\Exception\TagUnknown; +use PHPExiftool\Writer; class WriteMetadataJob extends AbstractJob { @@ -60,34 +62,17 @@ class WriteMetadataJob extends AbstractJob */ protected function doJob(JobData $data) { - $app = $data->getApplication(); $settings = simplexml_load_string($data->getTask()->getSettings()); $clearDoc = (Boolean) (string) $settings->cleardoc; $MWG = (Boolean) (string) $settings->mwg; - // move this in service provider configuration - // $app['exiftool.writer']->setModule(Writer::MODULE_MWG, true); + foreach ($data->getApplication()->getDataboxes() as $databox) { + $connection = $databox->get_connection(); - foreach ($app->getDataboxes() as $databox) { - - $conn = $databox->get_connection(); - $metaSubdefs = []; - - foreach ($databox->get_subdef_structure() as $type => $definitions) { - foreach ($definitions as $sub) { - $name = $sub->get_name(); - if ($sub->meta_writeable()) { - $metaSubdefs[$name . '_' . $type] = true; - } - } - } - - $sql = 'SELECT record_id, coll_id, jeton FROM record WHERE (jeton & ' . PhraseaTokens::WRITE_META . ' > 0)'; - - $stmt = $conn->prepare($sql); - $stmt->execute(); - $rs = $stmt->fetchAll(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); + $statement = $connection->prepare('SELECT record_id, coll_id, jeton FROM record WHERE (jeton & :token > 0)'); + $statement->execute(['token' => PhraseaTokens::WRITE_META]); + $rs = $statement->fetchAll(\PDO::FETCH_ASSOC); + $statement->closeCursor(); foreach ($rs as $row) { $record_id = $row['record_id']; @@ -99,10 +84,10 @@ class WriteMetadataJob extends AbstractJob $subdefs = []; foreach ($record->get_subdefs() as $name => $subdef) { $write_document = (($token & PhraseaTokens::WRITE_META_DOC) && $name == 'document'); - $write_subdef = (($token & PhraseaTokens::WRITE_META_SUBDEF) && isset($metaSubdefs[$name . '_' . $type])); + $write_subdef = (($token & PhraseaTokens::WRITE_META_SUBDEF) && $this->isSubdefMetadataUpdateRequired($databox, $type, $name)); if (($write_document || $write_subdef) && $subdef->is_physically_present()) { - $subdefs[$name] = $subdef->get_pathfile(); + $subdefs[$name] = $subdef->getRealPath(); } } @@ -179,16 +164,17 @@ class WriteMetadataJob extends AbstractJob ); } - $app['exiftool.writer']->reset(); + $writer = $this->getMetadataWriter($data->getApplication()); + $writer->reset(); if($MWG) { - $app['exiftool.writer']->setModule(ExifWriter::MODULE_MWG, true); + $writer->setModule(ExifWriter::MODULE_MWG, true); } foreach ($subdefs as $name => $file) { - $app['exiftool.writer']->erase($name != 'document' || $clearDoc, true); + $writer->erase($name != 'document' || $clearDoc, true); try { - $app['exiftool.writer']->write($file, $metadata); + $writer->write($file, $metadata); $this->log('info',sprintf('meta written for sbasid=%1$d - recordid=%2$d (%3$s)', $databox->get_sbas_id(), $record_id, $name)); } catch (PHPExiftoolException $e) { @@ -196,11 +182,33 @@ class WriteMetadataJob extends AbstractJob } } - $sql = 'UPDATE record SET jeton=jeton & ~' . PhraseaTokens::WRITE_META . ' WHERE record_id = :record_id'; - $stmt = $conn->prepare($sql); - $stmt->execute([':record_id' => $record_id]); - $stmt->closeCursor(); + $statement = $connection->prepare('UPDATE record SET jeton=jeton & ~:token WHERE record_id = :record_id'); + $statement->execute([ + 'record_id' => $record_id, + 'token' => PhraseaTokens::WRITE_META, + ]); + $statement->closeCursor(); } } } + + /** + * @param Application $app + * @return Writer + */ + private function getMetadataWriter(Application $app) + { + return $app['exiftool.writer']; + } + + /** + * @param \databox $databox + * @param string $subdefType + * @param string $subdefName + * @return bool + */ + private function isSubdefMetadataUpdateRequired(\databox $databox, $subdefType, $subdefName) + { + return $databox->get_subdef_structure()->get_subdef($subdefType, $subdefName)->isMetadataUpdateRequired(); + } } diff --git a/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php b/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php index 57f0f83a3a..0424b09a72 100644 --- a/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php +++ b/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php @@ -42,10 +42,35 @@ class PhraseanetExtension extends \Twig_Extension new \Twig_SimpleFunction('record_flags', array($this, 'getRecordFlags')), new \Twig_SimpleFunction('border_checker_from_fqcn', array($this, 'getCheckerFromFQCN')), new \Twig_SimpleFunction('caption_field', array($this, 'getCaptionField')), + new \Twig_SimpleFunction('caption_field_label', array($this, 'getCaptionFieldLabel')), new \Twig_SimpleFunction('caption_field_order', array($this, 'getCaptionFieldOrder')), ); } + /** + * get localized field's label + * @param RecordInterface $record + * @param $fieldName + * @return string - the name label + */ + public function getCaptionFieldLabel(RecordInterface $record, $fieldName) + { + if ($record) { + /** @var \appbox $appbox */ + $appbox = $this->app['phraseanet.appbox']; + $databox = $appbox->get_databox($record->getDataboxId()); + + foreach ($databox->get_meta_structure() as $meta) { + /** @var \databox_field $meta */ + if ($meta->get_name() === $fieldName) { + return $meta->get_label($this->app['locale']); + } + } + } + + return ''; + } + public function getCaptionField(RecordInterface $record, $field, $value) { if ($record instanceof ElasticsearchRecord) { @@ -248,7 +273,7 @@ class PhraseanetExtension extends \Twig_Extension } } elseif ($record instanceof \record_adapter) { if (null !== $thumbnail = $record->get_subdef($subdefName)) { - if ('' !== $path = $thumbnail->get_pathfile()) { + if ('' !== $path = $thumbnail->getRealPath()) { $etag = $thumbnail->getEtag(); return $staticMode->getUrl($path, $etag); } diff --git a/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php b/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php index cbb3de6c44..0eec23ed16 100644 --- a/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php +++ b/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php @@ -2,33 +2,68 @@ namespace Alchemy\Phrasea\Webhook; +use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Model\Entities\WebhookEvent; use Alchemy\Phrasea\Application; -use Alchemy\Phrasea\Webhook\Processor\FeedEntryProcessor; -use Alchemy\Phrasea\Webhook\Processor\UserRegistrationProcessor; +use Alchemy\Phrasea\Webhook\Processor\CallableProcessorFactory; +use Alchemy\Phrasea\Webhook\Processor\FeedEntryProcessorFactory; +use Alchemy\Phrasea\Webhook\Processor\ProcessorFactory; +use Alchemy\Phrasea\Webhook\Processor\UserRegistrationProcessorFactory; class EventProcessorFactory { - private $app; + /** + * @var ProcessorFactory + */ + private $processorFactories = []; + + /** + * @param Application $app + */ public function __construct(Application $app) { - $this->app = $app; + $this->registerFactory(WebhookEvent::FEED_ENTRY_TYPE, new FeedEntryProcessorFactory($app)); + $this->registerFactory(WebhookEvent::USER_REGISTRATION_TYPE, new UserRegistrationProcessorFactory($app)); } + /** + * @param string $eventType + * @param ProcessorFactory $processorFactory + */ + public function registerFactory($eventType, ProcessorFactory $processorFactory) + { + $this->processorFactories[$eventType] = $processorFactory; + } + + /** + * @param string $eventType + * @param callback|callable $callable + */ + public function registerCallableFactory($eventType, $callable) + { + if (! is_callable($callable)) { + throw new InvalidArgumentException(sprintf( + 'Expected a callable, got "%s" instead', + is_object($callable) ? get_class($callable) : gettype($callable) + )); + } + + $this->processorFactories[$eventType] = new CallableProcessorFactory($callable); + } + + /** + * @param WebhookEvent $event + * @return Processor\ProcessorInterface + */ public function get(WebhookEvent $event) { - switch ($event->getType()) { - case WebhookEvent::FEED_ENTRY_TYPE: - return new FeedEntryProcessor( - $this->app, - $this->app['repo.feed-entries'], - $this->app['phraseanet.user-query'] - ); - case WebhookEvent::USER_REGISTRATION_TYPE: - return new UserRegistrationProcessor($this->app['repo.users']); - default: - throw new \RuntimeException(sprintf('No processor found for %s', $event->getType())); + if (! isset($this->processorFactories[$event->getType()])) { + throw new \RuntimeException(sprintf('No processor found for %s', $event->getType())); } + + $factory = $this->processorFactories[$event->getType()]; + + return $factory->createProcessor(); } } diff --git a/lib/Alchemy/Phrasea/Webhook/Processor/CallableProcessorFactor.php b/lib/Alchemy/Phrasea/Webhook/Processor/CallableProcessorFactor.php new file mode 100644 index 0000000000..bc57c7c41d --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/Processor/CallableProcessorFactor.php @@ -0,0 +1,21 @@ +factoryMethod = $callable; + } + + public function createProcessor() + { + $factoryMethod = $this->factoryMethod; + + return $factoryMethod(); + } +} diff --git a/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessorFactory.php b/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessorFactory.php new file mode 100644 index 0000000000..69b2b1bf94 --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessorFactory.php @@ -0,0 +1,27 @@ +app = $application; + } + + public function createProcessor() + { + return new FeedEntryProcessor( + $this->app, + $this->app['repo.feed-entries'], + $this->app['phraseanet.user-query'] + ); + } +} diff --git a/lib/Alchemy/Phrasea/Webhook/Processor/ProcessorFactory.php b/lib/Alchemy/Phrasea/Webhook/Processor/ProcessorFactory.php new file mode 100644 index 0000000000..cdb3e90fd2 --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/Processor/ProcessorFactory.php @@ -0,0 +1,11 @@ +app = $application; + } + + /** + * @return UserRegistrationProcessor + */ + public function createProcessor() + { + return new UserRegistrationProcessor($this->app['repo.users']); + } +} diff --git a/lib/classes/ACL.php b/lib/classes/ACL.php index 65b805daff..b28c134af6 100644 --- a/lib/classes/ACL.php +++ b/lib/classes/ACL.php @@ -730,11 +730,7 @@ class ACL implements cache_cacheableInterface continue; } - try { - $ret[$base_id] = collection::get_from_base_id($this->app, $base_id); - } catch (\Exception $e) { - - } + $ret[$base_id] = $collection; } } @@ -1780,7 +1776,7 @@ class ACL implements cache_cacheableInterface $collections = []; foreach ($rs as $row) { - $collections[] = \collection::get_from_base_id($this->app, $row['base_id']); + $collections[] = \collection::getByBaseId($this->app, $row['base_id']); } return $collections; diff --git a/lib/classes/Bridge/Api.php b/lib/classes/Bridge/Api.php index dcd0886652..fa7d4553c9 100644 --- a/lib/classes/Bridge/Api.php +++ b/lib/classes/Bridge/Api.php @@ -99,16 +99,16 @@ class Bridge_Api } /** - * - * @return boolean + * @param null|DateTime $checkDate + * @return bool */ - public function is_disabled() + public function is_disabled(DateTime $checkDate = null) { if ($this->disable_time === null) { return false; } - $date_obj = new DateTime(); + $date_obj = $checkDate ?: new DateTime(); if ($date_obj > $this->disable_time) { $this->enable(); diff --git a/lib/classes/Bridge/Element.php b/lib/classes/Bridge/Element.php index cdf6673f21..84f0184446 100644 --- a/lib/classes/Bridge/Element.php +++ b/lib/classes/Bridge/Element.php @@ -14,85 +14,71 @@ use Alchemy\Phrasea\Application; class Bridge_Element { /** - * * @var appbox */ protected $app; /** - * * @var account */ protected $account; /** - * * @var int */ protected $id; /** - * * @var record_adapter */ protected $record; /** - * * @var string */ protected $dist_id; /** - * * @var string */ protected $status; /** - * * @var string */ protected $connector_status; /** - * * @var string */ protected $title; /** - * * @var string */ protected $type; /** - * * @var array */ protected $datas; /** - * * @var DateTime */ protected $created_on; /** - * * @var DateTime */ protected $uploaded_on; /** - * * @var DateTime */ protected $updated_on; /** - * * @var Bridge_Api_ElementInterface */ protected $connector_element; @@ -410,7 +396,6 @@ class Bridge_Element } /** - * * @return DateTime */ public function get_updated_on() diff --git a/lib/classes/appbox.php b/lib/classes/appbox.php index 8d01767ed8..6309532b82 100644 --- a/lib/classes/appbox.php +++ b/lib/classes/appbox.php @@ -10,13 +10,14 @@ */ use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Collection\CollectionService; use Alchemy\Phrasea\Core\Configuration\AccessRestriction; use Alchemy\Phrasea\Core\Connection\ConnectionSettings; use Alchemy\Phrasea\Core\Version\AppboxVersionRepository; +use Alchemy\Phrasea\Databox\DataboxConnectionProvider; use Alchemy\Phrasea\Databox\DataboxRepository; use Doctrine\ORM\Tools\SchemaTool; use MediaAlchemyst\Alchemyst; -use MediaAlchemyst\Specification\Image as ImageSpecification; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\File\File as SymfoFile; use Symfony\Component\Finder\Finder; @@ -42,6 +43,10 @@ class appbox extends base * @var \databox[] */ protected $databoxes; + /** + * @var CollectionService + */ + protected $collectionService; public function __construct(Application $app) { @@ -233,11 +238,11 @@ class appbox extends base protected function post_upgrade(Application $app) { - $this->apply_patches($this->get_version(), $app['phraseanet.version']->getNumber(), true, $app); + $this->apply_patches($this->get_version(), $app['phraseanet.version']->getNumber(), true); $this->setVersion($app['phraseanet.version']); foreach ($this->get_databoxes() as $databox) { - $databox->apply_patches($databox->get_version(), $app['phraseanet.version']->getNumber(), true, $app); + $databox->apply_patches($databox->get_version(), $app['phraseanet.version']->getNumber(), true); $databox->setVersion($app['phraseanet.version']); } @@ -258,12 +263,12 @@ class appbox extends base } /** - * @param $sbas_id + * @param int $sbas_id * @return databox */ public function get_databox($sbas_id) { - $databoxes = $this->get_databoxes(); + $databoxes = $this->getDataboxRepository()->findAll(); if (!isset($databoxes[$sbas_id]) && !array_key_exists($sbas_id, $databoxes)) { throw new NotFoundHttpException('Databox `' . $sbas_id . '` not found'); @@ -314,10 +319,23 @@ class appbox extends base parent::delete_data_from_cache($option); } + public function getCollectionService() + { + if ($this->collectionService === null) { + $this->collectionService = new CollectionService( + $this->app, + $this->connection, + new DataboxConnectionProvider($this) + ); + } + + return $this->collectionService; + } + /** * @return AccessRestriction */ - private function getAccessRestriction() + public function getAccessRestriction() { return $this->app['conf.restrictions']; } diff --git a/lib/classes/base.php b/lib/classes/base.php index 618e8d1325..4ffe971e60 100644 --- a/lib/classes/base.php +++ b/lib/classes/base.php @@ -263,10 +263,10 @@ abstract class base implements cache_cacheableInterface return $this; } - public function apply_patches($from, $to, $post_process, Application $app) + public function apply_patches($from, $to, $post_process) { $service = new DatabaseMaintenanceService($this->app, $this->connection); - return $service->applyPatches($this, $from, $to, $post_process, $app); + return $service->applyPatches($this, $from, $to, $post_process); } } diff --git a/lib/classes/caption/Field/Value.php b/lib/classes/caption/Field/Value.php index 28e259d713..d216de6f78 100644 --- a/lib/classes/caption/Field/Value.php +++ b/lib/classes/caption/Field/Value.php @@ -15,6 +15,9 @@ use Alchemy\Phrasea\Vocabulary; class caption_Field_Value implements cache_cacheableInterface { + const RETRIEVE_VALUES = true; + const DONT_RETRIEVE_VALUES = false; + /** @var int */ protected $id; @@ -55,16 +58,19 @@ class caption_Field_Value implements cache_cacheableInterface * @param databox_field $databox_field * @param record_adapter $record * @param mixed $id + * @param bool $retrieveValues * @return \caption_Field_Value */ - public function __construct(Application $app, databox_field $databox_field, record_adapter $record, $id) + public function __construct(Application $app, databox_field $databox_field, record_adapter $record, $id, $retrieveValues = self::RETRIEVE_VALUES) { $this->id = (int) $id; $this->databox_field = $databox_field; $this->record = $record; $this->app = $app; - $this->retrieveValues(); + if($retrieveValues == self::RETRIEVE_VALUES) { + $this->retrieveValues(); + } } public function getQjs() @@ -72,6 +78,53 @@ class caption_Field_Value implements cache_cacheableInterface return $this->qjs; } + public function injectValues($value, $VocabularyType, $VocabularyId) + { + $this->value = StringHelper::crlfNormalize($value); + + try { + $this->VocabularyType = $VocabularyType ? Vocabulary\Controller::get($this->app, $VocabularyType) : null; + $this->VocabularyId = $VocabularyId; + } catch (\InvalidArgumentException $e) { + + } + + if ($this->VocabularyType) { + /** + * Vocabulary Control has been deactivated + */ + if ( ! $this->databox_field->getVocabularyControl()) { + $this->removeVocabulary(); + } + /** + * Vocabulary Control has changed + */ + elseif ($this->databox_field->getVocabularyControl()->getType() !== $this->VocabularyType->getType()) { + $this->removeVocabulary(); + } + /** + * Current Id is not available anymore + */ + elseif ( ! $this->VocabularyType->validate($this->VocabularyId)) { + $this->removeVocabulary(); + } + /** + * String equivalence has changed + */ + elseif ($this->VocabularyType->getValue($this->VocabularyId) !== $this->value) { + $this->set_value($this->VocabularyType->getValue($this->VocabularyId)); + } + } + + $datas = [ + 'value' => $this->value, + 'vocabularyId' => $this->VocabularyId, + 'vocabularyType' => $this->VocabularyType ? $this->VocabularyType->getType() : null, + ]; + + $this->set_data_to_cache($datas); + } + protected function retrieveValues() { try { @@ -95,47 +148,13 @@ class caption_Field_Value implements cache_cacheableInterface $row = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); - $this->value = $row ? StringHelper::crlfNormalize($row['value']) : null; - - try { - $this->VocabularyType = $row['VocabularyType'] ? Vocabulary\Controller::get($this->app, $row['VocabularyType']) : null; - $this->VocabularyId = $row['VocabularyId']; - } catch (\InvalidArgumentException $e) { - + if($row) { + $this->injectValues($row['value'], $row['VocabularyType'], $row['VocabularyId']); } - - if ($this->VocabularyType) { - /** - * Vocabulary Control has been deactivated - */ - if ( ! $this->databox_field->getVocabularyControl()) { - $this->removeVocabulary(); - } - /** - * Vocabulary Control has changed - */ elseif ($this->databox_field->getVocabularyControl()->getType() !== $this->VocabularyType->getType()) { - $this->removeVocabulary(); - } - /** - * Current Id is not available anymore - */ elseif ( ! $this->VocabularyType->validate($this->VocabularyId)) { - $this->removeVocabulary(); - } - /** - * String equivalence has changed - */ elseif ($this->VocabularyType->getValue($this->VocabularyId) !== $this->value) { - $this->set_value($this->VocabularyType->getValue($this->VocabularyId)); - } + else { + $this->injectValues(null, null, null); } - $datas = [ - 'value' => $this->value, - 'vocabularyId' => $this->VocabularyId, - 'vocabularyType' => $this->VocabularyType ? $this->VocabularyType->getType() : null, - ]; - - $this->set_data_to_cache($datas); - return $this; } diff --git a/lib/classes/caption/field.php b/lib/classes/caption/field.php index fdd2bd658d..ecd4ac9bf2 100644 --- a/lib/classes/caption/field.php +++ b/lib/classes/caption/field.php @@ -14,6 +14,9 @@ use Doctrine\DBAL\Driver\Statement; class caption_field implements cache_cacheableInterface { + const RETRIEVE_VALUES = true; + const DONT_RETRIEVE_VALUES = false; + /** * @var databox_field */ @@ -33,33 +36,44 @@ class caption_field implements cache_cacheableInterface protected static $localCache = []; /** - * @param Application $app - * @param databox_field $databox_field + * @param Application $app + * @param databox_field $databox_field * @param record_adapter $record + * @param bool $retrieveValues * * @return caption_field */ - public function __construct(Application $app, databox_field $databox_field, \record_adapter $record) + public function __construct(Application $app, databox_field $databox_field, \record_adapter $record, $retrieveValues = self::RETRIEVE_VALUES) { $this->app = $app; $this->record = $record; $this->databox_field = $databox_field; $this->values = []; - $rs = $this->get_metadatas_ids(); + if($retrieveValues == self::RETRIEVE_VALUES) { + $rs = $this->get_metadatas_ids(); - foreach ($rs as $row) { - $this->values[$row['id']] = new caption_Field_Value($this->app, $databox_field, $record, $row['id']); + foreach ($rs as $row) { + $this->values[$row['id']] = new caption_Field_Value($this->app, $databox_field, $record, $row['id']); - // Inconsistent, should not happen - if ( ! $databox_field->is_multi()) { - break; + // Inconsistent, should not happen + if (!$databox_field->is_multi()) { + break; + } } } return $this; } + /** + * @param caption_Field_Value[] $values + */ + public function injectValues($values) + { + $this->values = $values; + } + protected function get_metadatas_ids() { try { @@ -73,7 +87,7 @@ class caption_field implements cache_cacheableInterface $sql = 'SELECT id FROM metadatas WHERE record_id = :record_id AND meta_struct_id = :meta_struct_id'; $params = [ - ':record_id' => $this->record->get_record_id() + ':record_id' => $this->record->getRecordId() , ':meta_struct_id' => $this->databox_field->get_id() ]; diff --git a/lib/classes/caption/record.php b/lib/classes/caption/record.php index 6dfeac7f92..740e21ee2b 100644 --- a/lib/classes/caption/record.php +++ b/lib/classes/caption/record.php @@ -47,7 +47,7 @@ class caption_record implements caption_interface, cache_cacheableInterface public function __construct(Application $app, \record_adapter $record, databox $databox) { $this->app = $app; - $this->sbas_id = $record->get_sbas_id(); + $this->sbas_id = $record->getDataboxId(); $this->record = $record; $this->databox = $databox; } @@ -78,12 +78,12 @@ class caption_record implements caption_interface, cache_cacheableInterface try { $fields = $this->get_data_from_cache(); } catch (\Exception $e) { - $sql = "SELECT m.id as meta_id, s.id as structure_id - FROM metadatas m, metadatas_structure s - WHERE m.record_id = :record_id AND s.id = m.meta_struct_id - ORDER BY s.sorter ASC"; + $sql = "SELECT m.id AS meta_id, s.id AS structure_id, value, VocabularyType, VocabularyId" + . " FROM metadatas m, metadatas_structure s" + . " WHERE m.record_id = :record_id AND s.id = m.meta_struct_id" + . " ORDER BY s.sorter ASC"; $stmt = $this->databox->get_connection()->prepare($sql); - $stmt->execute([':record_id' => $this->record->get_record_id()]); + $stmt->execute([':record_id' => $this->record->getRecordId()]); $fields = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); if ($fields) { @@ -95,11 +95,55 @@ class caption_record implements caption_interface, cache_cacheableInterface if ($fields) { $databox_descriptionStructure = $this->databox->get_meta_structure(); - foreach ($fields as $row) { - $databox_field = $databox_descriptionStructure->get_element($row['structure_id']); - $metadata = new caption_field($this->app, $databox_field, $this->record); - $rec_fields[$databox_field->get_id()] = $metadata; + // first group values by field + $caption_fields = []; + foreach ($fields as $row) { + $structure_id = $row['structure_id']; + if(!array_key_exists($structure_id, $caption_fields)) { + $caption_fields[$structure_id] = [ + 'db_field' => $databox_descriptionStructure->get_element($structure_id), + 'values' => [] + ]; + } + + if (count($caption_fields[$structure_id]['values']) > 0 && !$caption_fields[$structure_id]['db_field']->is_multi()) { + // Inconsistent, should not happen + continue; + } + + // build an EMPTY caption_Field_Value + $cfv = new caption_Field_Value( + $this->app, + $caption_fields[$structure_id]['db_field'], + $this->record, + $row['meta_id'], + caption_Field_Value::DONT_RETRIEVE_VALUES // ask caption_Field_Value "no n+1 sql" + ); + + // inject the value we already know + $cfv->injectValues($row['value'], $row['VocabularyType'], $row['VocabularyId']); + + // add the value to the field + $caption_fields[$structure_id]['values'][] = $cfv; + } + + // now build a "caption_field" with already known "caption_Field_Value"s + foreach($caption_fields as $structure_id => $caption_field) { + + // build an EMPTY caption_field + $cf = new caption_field( + $this->app, + $caption_field['db_field'], + $this->record, + caption_field::DONT_RETRIEVE_VALUES // ask caption_field "no n+1 sql" + ); + + // inject the value we already know + $cf->injectValues($caption_field['values']); + + // add the field to the fields + $rec_fields[$structure_id] = $cf; } } $this->fields = $rec_fields; @@ -248,7 +292,7 @@ class caption_record implements caption_interface, cache_cacheableInterface */ public function get_data_from_cache($option = null) { - return $this->record->get_databox()->get_data_from_cache($this->get_cache_key($option)); + return $this->record->getDatabox()->get_data_from_cache($this->get_cache_key($option)); } /** @@ -261,7 +305,7 @@ class caption_record implements caption_interface, cache_cacheableInterface */ public function set_data_to_cache($value, $option = null, $duration = 0) { - return $this->record->get_databox()->set_data_to_cache($value, $this->get_cache_key($option), $duration); + return $this->record->getDatabox()->set_data_to_cache($value, $this->get_cache_key($option), $duration); } /** @@ -274,6 +318,6 @@ class caption_record implements caption_interface, cache_cacheableInterface { $this->fields = null; - return $this->record->get_databox()->delete_data_from_cache($this->get_cache_key($option)); + return $this->record->getDatabox()->delete_data_from_cache($this->get_cache_key($option)); } } diff --git a/lib/classes/collection.php b/lib/classes/collection.php index c37e5de801..7ac76177bb 100644 --- a/lib/classes/collection.php +++ b/lib/classes/collection.php @@ -10,11 +10,15 @@ */ use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Collection\Collection as CollectionVO; +use Alchemy\Phrasea\Collection\CollectionRepository; +use Alchemy\Phrasea\Collection\CollectionRepositoryRegistry; +use Alchemy\Phrasea\Collection\CollectionService; +use Alchemy\Phrasea\Collection\Reference\CollectionReference; +use Alchemy\Phrasea\Collection\Reference\CollectionReferenceRepository; use Alchemy\Phrasea\Core\Thumbnail\ThumbnailedElement; use Alchemy\Phrasea\Core\Thumbnail\ThumbnailManager; -use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Model\Entities\User; -use Doctrine\DBAL\Driver\Connection; use Symfony\Component\HttpFoundation\File\File; use Alchemy\Phrasea\Core\Event\Collection\CollectionEvent; @@ -29,205 +33,347 @@ use Alchemy\Phrasea\Core\Event\Collection\UnmountedEvent; use Alchemy\Phrasea\Core\Event\Collection\SettingsChangedEvent; use Alchemy\Phrasea\Core\Event\Collection\LabelChangedEvent; -class collection implements cache_cacheableInterface, ThumbnailedElement +class collection implements ThumbnailedElement, cache_cacheableInterface { - protected $base_id; - protected $sbas_id; - protected $coll_id; - protected $available = false; - protected $name; - protected $prefs; - protected $pub_wm; - protected $labels = []; - private static $_logos = []; - private static $_stamps = []; - private static $_watermarks = []; - private static $_presentations = []; - private static $_collections = []; - protected $databox; - protected $is_active; - protected $binary_logo; - protected $ord; - protected $app; const PIC_LOGO = 'minilogos'; const PIC_WM = 'wm'; const PIC_STAMP = 'stamp'; const PIC_PRESENTATION = 'presentation'; - protected function __construct(Application $app, $coll_id, databox $databox) - { - $this->app = $app; - $this->databox = $databox; - $this->sbas_id = (int) $databox->get_sbas_id(); - $this->coll_id = (int) $coll_id; - $this->load(); + private static $_logos = []; + private static $_stamps = []; + private static $_watermarks = []; + private static $_presentations = []; - return $this; + /** + * @param Application $app + * @param $databoxId + * @return CollectionRepository + */ + private static function getRepository(Application $app, $databoxId) + { + /** @var CollectionRepositoryRegistry $registry */ + $registry = $app['repo.collections-registry']; + + return $registry->getRepositoryByDatabox($databoxId); } + public static function create(Application $app, databox $databox, appbox $appbox, $name, User $user = null) + { + $databoxId = $databox->get_sbas_id(); + + $repository = self::getRepository($app, $databoxId); + $collection = new CollectionVO($databoxId, 0, $name); + + $repository->save($collection); + + $repository = $app['repo.collection-references']; + $collectionReference = new CollectionReference(0, $databoxId, $collection->getCollectionId(), 0, true, ''); + + $repository->save($collectionReference); + + $app['repo.collections-registry']->purgeRegistry(); + + $collection = new self($app, $collection, $collectionReference); + + if (null !== $user) { + $collection->collectionService->grantAdminRights($collectionReference, $user); + } + + $app['dispatcher']->dispatch( + CollectionEvents::CREATED, + new CreatedEvent( + $collection + ) + ); + + return $collection; + } + + public static function mount_collection(Application $app, databox $databox, $coll_id, User $user) + { + $reference = new CollectionReference(0, $databox->get_sbas_id(), $coll_id, 0, true, ''); + + $app['repo.collection-references']->save($reference); + $app['repo.collections-registry']->purgeRegistry(); + + $collection = self::getByBaseId($app, $reference->getBaseId()); + $collection->collectionService->grantAdminRights($collection->reference, $user); + + $app['dispatcher']->dispatch( + CollectionEvents::MOUNTED, + new MountedEvent( + $collection + ) + ); + + return $reference->getBaseId(); + } + + public static function getLogo($base_id, Application $app, $printname = false) + { + $base_id_key = $base_id . '_' . ($printname ? '1' : '0'); + + if (!isset(self::$_logos[$base_id_key])) { + + if (is_file($app['root.path'] . '/config/minilogos/' . $base_id)) { + $name = phrasea::bas_labels($base_id, $app); + self::$_logos[$base_id_key] = ''; + } elseif ($printname) { + self::$_logos[$base_id_key] = phrasea::bas_labels($base_id, $app); + } + } + + return isset(self::$_logos[$base_id_key]) ? self::$_logos[$base_id_key] : ''; + } + + public static function getWatermark($base_id) + { + if (!isset(self::$_watermarks['base_id'])) { + + if (is_file(__DIR__ . '/../../config/wm/' . $base_id)) { + self::$_watermarks['base_id'] = ''; + } + } + + return isset(self::$_watermarks['base_id']) ? self::$_watermarks['base_id'] : ''; + } + + public static function getPresentation($base_id) + { + if (!isset(self::$_presentations['base_id'])) { + + if (is_file(__DIR__ . '/../../config/presentation/' . $base_id)) { + self::$_presentations['base_id'] = ''; + } + } + + return isset(self::$_presentations['base_id']) ? self::$_presentations['base_id'] : ''; + } + + public static function getStamp($base_id) + { + if (!isset(self::$_stamps['base_id'])) { + + if (is_file(__DIR__ . '/../../config/stamp/' . $base_id)) { + self::$_stamps['base_id'] = ''; + } + } + + return isset(self::$_stamps['base_id']) ? self::$_stamps['base_id'] : ''; + } + + public static function purge() + { + // BC only + } + + /** + * @param Application $app + * @param int $base_id + * @return collection + */ + public static function getByBaseId(Application $app, $base_id) + { + /** @var CollectionReferenceRepository $referenceRepository */ + $referenceRepository = $app['repo.collection-references']; + $reference = $referenceRepository->find($base_id); + + if (!$reference) { + throw new Exception_Databox_CollectionNotFound(sprintf( + "Collection with base_id %s could not be found", + $base_id + )); + } + + return self::getAvailableCollection($app, $reference->getDataboxId(), $reference->getCollectionId()); + } + + /** + * @param Application $app + * @param databox $databox + * @param int $collectionId + * @return collection + */ + public static function getByCollectionId(Application $app, databox $databox, $collectionId) + { + assert(is_int($collectionId)); + + return self::getAvailableCollection($app, $databox->get_sbas_id(), $collectionId); + } + + /** + * @param Application $app + * @return \Alchemy\Phrasea\Core\Configuration\AccessRestriction + */ + private static function getAccessRestriction(Application $app) + { + return $app['conf.restrictions']; + } + + private static function assertCollectionIsAvailable(Application $app, collection $collection) + { + if (!self::getAccessRestriction($app)->isCollectionAvailable($collection)) { + throw new Exception_Databox_CollectionNotFound(sprintf( + 'Collection `%d` is not available here.', + $collection->get_base_id() + )); + } + } + + /** + * @param Application $app + * @param int $databoxId + * @param int $collectionId + * @return collection + */ + private static function getByDataboxIdAndCollectionId(Application $app, $databoxId, $collectionId) + { + $repository = self::getRepository($app, $databoxId); + $collection = $repository->find($collectionId); + + if (!$collection) { + throw new Exception_Databox_CollectionNotFound(sprintf( + "Collection '%d' on databox '%d' could not be found", + $collectionId, + $databoxId + )); + } + + return $collection; + } + + /** + * @param Application $app + * @param int $databoxId + * @param int $collectionId + * @return collection + */ + private static function getAvailableCollection(Application $app, $databoxId, $collectionId) + { + $collection = self::getByDataboxIdAndCollectionId($app, $databoxId, $collectionId); + self::assertCollectionIsAvailable($app, $collection); + + return $collection; + } + + /** + * @var Application + */ + protected $app; + + /** + * @var CollectionService + */ + protected $collectionService; + + /** + * @var databox + */ + protected $databox; + + /** + * @var CollectionVO + */ + protected $collectionVO; + + /** + * @var CollectionRepositoryRegistry + */ + protected $collectionRepositoryRegistry; + + /** + * @var CollectionReference + */ + protected $reference; + + + /** + * @param Application $app + * @param CollectionVO $collection + * @param CollectionReference $reference + * @internal param $baseId + * @internal param array $row + */ + public function __construct(Application $app, CollectionVO $collection, CollectionReference $reference) + { + $this->collectionVO = $collection; + $this->reference = $reference; + + $this->fetchInternalServices($app); + } + + /** + * @param $eventName + * @param CollectionEvent $event + */ private function dispatch($eventName, CollectionEvent $event) { $this->app['dispatcher']->dispatch($eventName, $event); } - protected function load() + /** + * @return CollectionRepository + */ + private function getCollectionRepository() { - try { - $datas = $this->get_data_from_cache(); - if (!is_array($datas)) { - throw new \Exception('Collection could not be retrieved from cache'); - } - $this->is_active = $datas['is_active']; - $this->base_id = $datas['base_id']; - $this->available = $datas['available']; - $this->pub_wm = $datas['pub_wm']; - $this->name = $datas['name']; - $this->ord = $datas['ord']; - $this->prefs = $datas['prefs']; - $this->labels = $datas['labels']; + return self::getRepository($this->app, $this->reference->getDataboxId()); + } - return $this; - } catch (\Exception $e) { + /** + * @return CollectionReferenceRepository + */ + private function getReferenceRepository() + { + return $this->app['repo.collection-references']; + } - } + public function hydrate(Application $app) + { + $this->fetchInternalServices($app); + } - $connbas = $this->databox->get_connection(); - $sql = 'SELECT - asciiname, prefs, pub_wm, coll_id, - label_en, label_fr, label_de, label_nl - FROM coll WHERE coll_id = :coll_id'; - $stmt = $connbas->prepare($sql); - $stmt->execute([':coll_id' => $this->coll_id]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if ( ! $row) - throw new Exception('Unknown collection ' . $this->coll_id . ' on ' . $this->databox->get_dbname()); - - $this->available = true; - $this->pub_wm = $row['pub_wm']; - $this->name = $row['asciiname']; - $this->prefs = $row['prefs']; - $this->labels = [ - 'fr' => $row['label_fr'], - 'en' => $row['label_en'], - 'de' => $row['label_de'], - 'nl' => $row['label_nl'], + public function __sleep() + { + return [ + 'collectionVO', + 'reference' ]; + } - $conn = $this->app->getApplicationBox()->get_connection(); - - $sql = 'SELECT server_coll_id, sbas_id, base_id, active, ord FROM bas - WHERE server_coll_id = :coll_id AND sbas_id = :sbas_id'; - - $stmt = $conn->prepare($sql); - $stmt->execute([':coll_id' => $this->coll_id, ':sbas_id' => $this->databox->get_sbas_id()]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $this->is_active = false; - - if ($row) { - $this->is_active = ! ! $row['active']; - $this->base_id = (int) $row['base_id']; - $this->ord = (int) $row['ord']; - } - - $stmt->closeCursor(); - - $datas = [ - 'is_active' => $this->is_active - , 'base_id' => $this->base_id - , 'available' => $this->available - , 'pub_wm' => $this->pub_wm - , 'name' => $this->name - , 'ord' => $this->ord - , 'prefs' => $this->prefs - , 'labels' => $this->labels + public function __debugInfo() + { + return [ + 'reference' => $this->reference, + 'databox' => $this->databox, + 'collectionVO' => $this->collectionVO ]; - - $this->set_data_to_cache($datas); - - return $this; } - public function enable(appbox $appbox) + /** + * @return CollectionVO + */ + public function getCollection() { - $sql = 'UPDATE bas SET active = "1" WHERE base_id = :base_id'; - $stmt = $appbox->get_connection()->prepare($sql); - $stmt->execute([':base_id' => $this->get_base_id()]); - $stmt->closeCursor(); - - $this->is_active = true; - $this->delete_data_from_cache(); - $appbox->delete_data_from_cache(appbox::CACHE_LIST_BASES); - $this->databox->delete_data_from_cache(databox::CACHE_COLLECTIONS); - cache_databox::update($this->app, $this->databox->get_sbas_id(), 'structure'); - - $this->dispatch(CollectionEvents::ENABLED, new EnabledEvent($this)); - - return $this; + return $this->collectionVO; } - public function get_ord() + /** + * @return CollectionReference + */ + public function getReference() { - return $this->ord; - } - - public function set_ord($ord) - { - $this->app->getApplicationBox()->set_collection_order($this, $ord); - $this->delete_data_from_cache(); - $this->app->getApplicationBox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); - - return $this; - } - - public function disable(appbox $appbox) - { - $sql = 'UPDATE bas SET active=0 WHERE base_id = :base_id'; - $stmt = $appbox->get_connection()->prepare($sql); - $stmt->execute([':base_id' => $this->get_base_id()]); - $stmt->closeCursor(); - $this->is_active = false; - $this->delete_data_from_cache(); - $appbox->delete_data_from_cache(appbox::CACHE_LIST_BASES); - $this->databox->delete_data_from_cache(databox::CACHE_COLLECTIONS); - cache_databox::update($this->app, $this->databox->get_sbas_id(), 'structure'); - - $this->dispatch(CollectionEvents::DISABLED, new DisabledEvent($this)); - - return $this; - } - - public function empty_collection($pass_quantity = 100) - { - $pass_quantity = (int) $pass_quantity > 200 ? 200 : (int) $pass_quantity; - $pass_quantity = (int) $pass_quantity < 10 ? 10 : (int) $pass_quantity; - - $sql = "SELECT record_id FROM record WHERE coll_id = :coll_id - ORDER BY record_id DESC LIMIT 0, " . $pass_quantity; - - $stmt = $this->databox->get_connection()->prepare($sql); - $stmt->execute([':coll_id' => $this->get_coll_id()]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - foreach ($rs as $row) { - $record = $this->databox->get_record($row['record_id']); - $record->delete(); - unset($record); - } - - $this->dispatch(CollectionEvents::EMPTIED, new EmptiedEvent($this)); - - return $this; + return $this->reference; } + /** + * @return bool + */ public function is_active() { - return $this->is_active; + return $this->reference->isActive(); } /** @@ -239,16 +385,257 @@ class collection implements cache_cacheableInterface, ThumbnailedElement return $this->databox; } + /** + * @return \Doctrine\DBAL\Connection + */ public function get_connection() { return $this->databox->get_connection(); } - public function getRootIdentifier() + /** + * @param $publi + * @return $this + */ + public function set_public_presentation($publi) { - return $this->base_id; + $this->collectionVO->setPublicWatermark($publi); + $this->getCollectionRepository()->save($this->collectionVO); + $this->app['repo.collections-registry']->purgeRegistry(); + + return $this; } + /** + * @param $name + * @return $this + * @throws Exception_InvalidArgument + */ + public function set_name($name) + { + $old_name = $this->get_name(); + + try { + $this->collectionVO->setName($name); + } catch (\InvalidArgumentException $e) { + throw new Exception_InvalidArgument(); + } + + $this->getCollectionRepository()->save($this->collectionVO); + $this->app['repo.collections-registry']->purgeRegistry(); + + $this->dispatch(CollectionEvents::NAME_CHANGED, + new NameChangedEvent( + $this, + array("name_before"=>$old_name) + ) + ); + + return $this; + } + + /** + * @param $code + * @param $label + * @return $this + */ + public function set_label($code, $label) + { + $old_label = $this->collectionVO->getLabel($code); + + $this->collectionVO->setLabel($code, $label); + + $this->getCollectionRepository()->save($this->collectionVO); + $this->app['repo.collections-registry']->purgeRegistry(); + + $this->dispatch(CollectionEvents::LABEL_CHANGED, new LabelChangedEvent($this, array( + "lng"=>$code, + "label_before"=>$old_label, + ))); + + return $this; + } + + /** + * @param $code + * @param bool $substitute + * @return string + */ + public function get_label($code, $substitute = true) + { + return $this->collectionVO->getLabel($code, $substitute); + } + + /** + * @return int + */ + public function get_ord() + { + return $this->reference->getDisplayIndex(); + } + + /** + * @param $ord + * @return $this + */ + public function set_ord($ord) + { + $this->reference->setDisplayIndex($ord); + + $this->getReferenceRepository()->save($this->reference); + $this->app['repo.collections-registry']->purgeRegistry(); + + return $this; + } + + /** + * @return int[]|null|string + */ + public function get_binary_minilogos() + { + return $this->collectionVO->getLogo(); + } + + /** + * @return int + */ + public function get_base_id() + { + return (int) $this->reference->getBaseId(); + } + + /** + * @return int + */ + public function get_sbas_id() + { + return (int) $this->reference->getDataboxId(); + } + + /** + * @return int + */ + public function get_coll_id() + { + return (int) $this->reference->getCollectionId(); + } + + /** + * @return string + */ + public function get_prefs() + { + return $this->collectionVO->getPreferences(); + } + + /** + * @param DOMDocument $dom + * @return string + */ + public function set_prefs(DOMDocument $dom) + { + $oldPreferences = $this->collectionVO->getPreferences(); + + $this->collectionVO->setPreferences($dom->saveXML()); + $this->getCollectionRepository()->save($this->collectionVO); + + $this->app['repo.collections-registry']->purgeRegistry(); + + $this->dispatch( + CollectionEvents::SETTINGS_CHANGED, + new SettingsChangedEvent( + $this, + array( + 'settings_before' => $oldPreferences + ) + ) + ); + + return $this->collectionVO->getPreferences(); + } + + /** + * @return string + */ + public function get_name() + { + return $this->collectionVO->getName(); + } + + /** + * @return string + */ + public function get_pub_wm() + { + return $this->collectionVO->getName(); + } + + /** + * @return bool + */ + public function is_available() + { + return true; + } + + /** + * @return int + */ + public function getRootIdentifier() + { + return $this->reference->getBaseId(); + } + + /** + * @return $this + */ + public function disable() + { + $this->reference->disable(); + + $this->getReferenceRepository()->save($this->reference); + $this->collectionRepositoryRegistry->purgeRegistry(); + + cache_databox::update($this->app, $this->databox->get_sbas_id(), 'structure'); + + $this->dispatch(CollectionEvents::DISABLED, new DisabledEvent($this)); + + return $this; + } + + /** + * @return $this + */ + public function enable() + { + $this->reference->enable(); + + $this->getReferenceRepository()->save($this->reference); + $this->collectionRepositoryRegistry->purgeRegistry(); + + cache_databox::update($this->app, $this->databox->get_sbas_id(), 'structure'); + + $this->dispatch(CollectionEvents::ENABLED, new EnabledEvent($this)); + + return $this; + } + + /** + * @param int $pass_quantity + * @return $this + * @throws \Doctrine\DBAL\DBALException + */ + public function empty_collection($pass_quantity = 100) + { + $this->collectionService->emptyCollection($this->databox, $this->collectionVO, $pass_quantity); + $this->dispatch(CollectionEvents::EMPTIED, new EmptiedEvent($this)); + return $this; + } + + /** + * @param string $thumbnailType + * @param File $file + */ public function updateThumbnail($thumbnailType, File $file = null) { switch ($thumbnailType) { @@ -268,378 +655,109 @@ class collection implements cache_cacheableInterface, ThumbnailedElement } } - - public function set_public_presentation($publi) - { - if (in_array($publi, ['none', 'wm', 'stamp'])) { - $sql = 'UPDATE coll SET pub_wm = :pub_wm WHERE coll_id = :coll_id'; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':pub_wm' => $publi, ':coll_id' => $this->get_coll_id()]); - $stmt->closeCursor(); - - $this->pub_wm = $publi; - - $this->delete_data_from_cache(); - } - - return $this; - } - - public function set_name($name) - { - $old_name = $this->get_name(); - - $name = trim(strip_tags($name)); - - if ($name === '') - throw new Exception_InvalidArgument (); - - $sql = "UPDATE coll SET asciiname = :asciiname - WHERE coll_id = :coll_id"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':asciiname' => $name, ':coll_id' => $this->get_coll_id()]); - $stmt->closeCursor(); - - $this->name = $name; - - $this->delete_data_from_cache(); - - phrasea::reset_baseDatas($this->databox->get_appbox()); - - $this->dispatch( - CollectionEvents::NAME_CHANGED, - new NameChangedEvent( - $this, - array("name_before"=>$old_name) - ) - ); - - return $this; - } - - public function set_label($code, $label) - { - if (!array_key_exists($code, $this->labels)) { - throw new InvalidArgumentException(sprintf('Code %s is not defined', $code)); - } - - $old_label = $this->labels[$code]; - - $sql = "UPDATE coll SET label_$code = :label - WHERE coll_id = :coll_id"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':label' => $label, ':coll_id' => $this->get_coll_id()]); - $stmt->closeCursor(); - - $this->labels[$code] = $label; - - $this->delete_data_from_cache(); - - phrasea::reset_baseDatas($this->databox->get_appbox()); - - $this->dispatch(CollectionEvents::LABEL_CHANGED, new LabelChangedEvent($this, array( - "lng"=>$code, - "label_before"=>$old_label, - ))); - - return $this; - } - - public function get_label($code, $substitute = true) - { - if (!array_key_exists($code, $this->labels)) { - throw new InvalidArgumentException(sprintf('Code %s is not defined', $code)); - } - - if ($substitute) { - return isset($this->labels[$code]) ? $this->labels[$code] : $this->name; - } else { - return $this->labels[$code]; - } - } - + /** + * @return int|null + * @throws \Doctrine\DBAL\DBALException + */ public function get_record_amount() { - $sql = "SELECT COUNT(record_id) AS n FROM record WHERE coll_id = :coll_id"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':coll_id' => $this->get_coll_id()]); - $rowbas = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $amount = $rowbas ? (int) $rowbas["n"] : null; - - return $amount; + return $this->collectionService->getRecordCount($this->collectionVO); } + /** + * @return array + * @throws \Doctrine\DBAL\DBALException + */ public function get_record_details() { - - $sql = "SELECT record.coll_id,name,COALESCE(asciiname, CONCAT('_',record.coll_id)) AS asciiname, - SUM(1) AS n, SUM(size) AS size - FROM record NATURAL JOIN subdef - INNER JOIN coll ON record.coll_id=coll.coll_id AND coll.coll_id = :coll_id - GROUP BY record.coll_id, subdef.name"; - - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':coll_id' => $this->get_coll_id()]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $ret = []; - foreach ($rs as $row) { - $ret[] = [ - "coll_id" => (int) $row["coll_id"], - "name" => $row["name"], - "amount" => (int) $row["n"], - "size" => (int) $row["size"]]; - } - - return $ret; + return $this->collectionService->getRecordDetails($this->collectionVO); } + /** + * @param SplFileInfo $pathfile + * @return $this + */ public function update_logo(\SplFileInfo $pathfile = null) { - if (is_null($pathfile)) { - $this->binary_logo = null; - } else { - $this->binary_logo = file_get_contents($pathfile->getPathname()); + $fileContents = null; + + if (!is_null($pathfile)) { + $fileContents = file_get_contents($pathfile->getPathname()); } - $sql = "UPDATE coll SET logo = :logo, majLogo=NOW() WHERE coll_id = :coll_id"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':logo' => $this->binary_logo, ':coll_id' => $this->get_coll_id()]); - $stmt->closeCursor(); + $this->collectionVO->setLogo($fileContents); + + $this->getCollectionRepository()->save($this->collectionVO); + $this->collectionRepositoryRegistry->purgeRegistry(); return $this; } + /** + * @return $this + * @throws \Doctrine\DBAL\DBALException + */ public function reset_watermark() { + $this->collectionService->resetWatermark($this->collectionVO); - $sql = 'SELECT path, file FROM record r INNER JOIN subdef s USING(record_id) - WHERE r.coll_id = :coll_id AND r.type="image" AND s.name="preview"'; - - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':coll_id' => $this->get_coll_id()]); - - while ($row2 = $stmt->fetch(PDO::FETCH_ASSOC)) { - @unlink(p4string::addEndSlash($row2['path']) . 'watermark_' . $row2['file']); - } - $stmt->closeCursor(); + $this->getCollectionRepository()->save($this->collectionVO); + $this->collectionRepositoryRegistry->purgeRegistry(); return $this; } + /** + * @param null $record_id + * @return $this + * @throws \Doctrine\DBAL\DBALException + */ public function reset_stamp($record_id = null) { + $this->collectionService->resetStamp($this->collectionVO, $record_id); - $sql = 'SELECT path, file FROM record r INNER JOIN subdef s USING(record_id) - WHERE r.coll_id = :coll_id - AND r.type="image" AND s.name IN ("preview", "document")'; - - $params = [':coll_id' => $this->get_coll_id()]; - - if ($record_id) { - $sql .= ' AND record_id = :record_id'; - $params[':record_id'] = $record_id; - } - - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute($params); - - while ($row2 = $stmt->fetch(PDO::FETCH_ASSOC)) { - @unlink(p4string::addEndSlash($row2['path']) . 'stamp_' . $row2['file']); - } - $stmt->closeCursor(); + $this->getCollectionRepository()->save($this->collectionVO); + $this->collectionRepositoryRegistry->purgeRegistry(); return $this; } + /** + * @throws \Doctrine\DBAL\DBALException + */ public function delete() { - while ($this->get_record_amount() > 0) { - $this->empty_collection(); - } + $this->collectionService->delete($this->databox, $this->collectionVO, $this->reference); - $sql = "DELETE FROM coll WHERE coll_id = :coll_id"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':coll_id' => $this->get_coll_id()]); - $stmt->closeCursor(); - - $appbox = $this->databox->get_appbox(); - - $sql = "DELETE FROM bas WHERE base_id = :base_id"; - $stmt = $appbox->get_connection()->prepare($sql); - $stmt->execute([':base_id' => $this->get_base_id()]); - $stmt->closeCursor(); - - $sql = "DELETE FROM basusr WHERE base_id = :base_id"; - $stmt = $appbox->get_connection()->prepare($sql); - $stmt->execute([':base_id' => $this->get_base_id()]); - $stmt->closeCursor(); + $this->getCollectionRepository()->delete($this->collectionVO); $this->app['manipulator.registration']->deleteRegistrationsOnCollection($this); - - $this->get_databox()->delete_data_from_cache(databox::CACHE_COLLECTIONS); - $appbox->delete_data_from_cache(appbox::CACHE_LIST_BASES); - phrasea::reset_baseDatas($appbox); - - return; - } - - public function get_binary_minilogos() - { - return $this->binary_logo; + $this->collectionRepositoryRegistry->purgeRegistry(); } /** - * - * @param Application $app - * @param int $base_id - * @return collection + * @return $this + * @throws \Doctrine\DBAL\DBALException */ - public static function get_from_base_id(Application $app, $base_id) - { - $coll_id = phrasea::collFromBas($app, $base_id); - $sbas_id = phrasea::sbasFromBas($app, $base_id); - if (! $sbas_id || ! $coll_id) { - throw new Exception_Databox_CollectionNotFound(sprintf("Collection with base_id %s could not be found", $base_id)); - } - $databox = $app->findDataboxById($sbas_id); - - return self::get_from_coll_id($app, $databox, $coll_id); - } - - /** - * - * @param Application $app - * @param databox $databox - * @param int $coll_id - * @return collection - */ - public static function get_from_coll_id(Application $app, databox $databox, $coll_id) - { - assert(is_int($coll_id)); - - $key = sprintf('%d_%d', $databox->get_sbas_id(), $coll_id); - if (!isset(self::$_collections[$key])) { - $collection = new self($app, $coll_id, $databox); - - if (!$app['conf.restrictions']->isCollectionAvailable($collection)) { - throw new Exception_Databox_CollectionNotFound('Collection `' . $collection->get_base_id() . '` is not available here.'); - } - - self::$_collections[$key] = $collection; - } - - return self::$_collections[$key]; - } - - public function get_base_id() - { - return $this->base_id; - } - - public function get_sbas_id() - { - return $this->sbas_id; - } - - public function get_coll_id() - { - return $this->coll_id; - } - - public function get_prefs() - { - return $this->prefs; - } - - public function set_prefs(DOMDocument $dom) - { - $old_prefs = $this->get_prefs(); - - $this->prefs = $dom->saveXML(); - - $sql = "UPDATE coll SET prefs = :prefs WHERE coll_id = :coll_id"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute([':prefs' => $this->prefs, ':coll_id' => $this->get_coll_id()]); - $stmt->closeCursor(); - - $this->delete_data_from_cache(); - - $this->dispatch( - CollectionEvents::SETTINGS_CHANGED, - new SettingsChangedEvent( - $this, - array( - 'settings_before'=>$old_prefs - ) - ) - ); - - return $this->prefs; - } - - public function get_name() - { - return $this->name; - } - - public function get_pub_wm() - { - return $this->pub_wm; - } - - public function is_available() - { - return $this->available; - } - - public function unmount_collection(Application $app) + public function unmount() { $old_coll_id = $this->get_coll_id(); $old_name = $this->get_name(); - $params = [':base_id' => $this->get_base_id()]; + $this->collectionService->unmountCollection($this->reference); - $query = $app['phraseanet.user-query']; - $total = $query->on_base_ids([$this->get_base_id()]) - ->include_phantoms(false) - ->include_special_users(true) - ->include_invite(true) - ->include_templates(true)->get_total(); - $n = 0; - while ($n < $total) { - $results = $query->limit($n, 50)->execute()->get_results(); - foreach ($results as $user) { - $app->getAclForUser($user)->delete_data_from_cache(ACL::CACHE_RIGHTS_SBAS); - $app->getAclForUser($user)->delete_data_from_cache(ACL::CACHE_RIGHTS_BAS); - } - $n+=50; - } - - $sql = "DELETE FROM basusr WHERE base_id = :base_id"; - $stmt = $app->getApplicationBox()->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $sql = "DELETE FROM bas WHERE base_id = :base_id"; - $stmt = $app->getApplicationBox()->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); + $this->getReferenceRepository()->delete($this->reference); $this->app['manipulator.registration']->deleteRegistrationsOnCollection($this); + $this->collectionRepositoryRegistry->purgeRegistry(); - phrasea::reset_baseDatas($app['phraseanet.appbox']); - - $app['dispatcher']->dispatch( + $this->dispatch( CollectionEvents::UNMOUNTED, new UnmountedEvent( null, // the coll is not available anymore array( - 'coll_id'=>$old_coll_id, - 'coll_name'=>$old_name + 'coll_id' => $old_coll_id, + 'coll_name' => $old_name ) ) ); @@ -647,218 +765,6 @@ class collection implements cache_cacheableInterface, ThumbnailedElement return $this; } - private static function getNewOrder(Connection $conn, $sbas_id) - { - $sql = "SELECT GREATEST(0, MAX(ord)) + 1 AS ord FROM bas WHERE sbas_id = :sbas_id"; - $stmt = $conn->prepare($sql); - $stmt->execute([':sbas_id' => $sbas_id]); - $ord = $stmt->fetch(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - return $ord['ord'] ?: 1; - } - - public static function create(Application $app, databox $databox, appbox $appbox, $name, User $user = null) - { - $sbas_id = $databox->get_sbas_id(); - $connbas = $databox->get_connection(); - $conn = $appbox->get_connection(); - $new_bas = false; - - $prefs = ' - - 0 - - - '; - - $sql = "INSERT INTO coll (coll_id, asciiname, prefs, logo) - VALUES (null, :name, :prefs, '')"; - - $params = [ - ':name' => $name, - 'prefs' => $prefs, - ]; - - $stmt = $connbas->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $new_id = (int) $connbas->lastInsertId(); - - $sql = "INSERT INTO bas (base_id, active, ord, server_coll_id, sbas_id, aliases) - VALUES - (null, 1, :ord, :server_coll_id, :sbas_id, '')"; - $stmt = $conn->prepare($sql); - $stmt->execute([ - ':server_coll_id' => $new_id, - ':sbas_id' => $sbas_id, - ':ord' => self::getNewOrder($conn, $sbas_id), - ]); - $stmt->closeCursor(); - - $new_bas = $conn->lastInsertId(); - $databox->delete_data_from_cache(databox::CACHE_COLLECTIONS); - $appbox->delete_data_from_cache(appbox::CACHE_LIST_BASES); - - phrasea::reset_baseDatas($appbox); - - $collection = self::get_from_coll_id($app, $databox, $new_id); - - if (null !== $user) { - $collection->set_admin($new_bas, $user); - } - - $app['dispatcher']->dispatch( - CollectionEvents::CREATED, - new CreatedEvent( - $collection - ) - ); - - return $collection; - } - - public function set_admin($base_id, User $user) - { - - $rights = [ - "canputinalbum" => "1", - "candwnldhd" => "1", - "nowatermark" => "1", - "candwnldpreview" => "1", - "cancmd" => "1", - "canadmin" => "1", - "actif" => "1", - "canreport" => "1", - "canpush" => "1", - "basusr_infousr" => "", - "canaddrecord" => "1", - "canmodifrecord" => "1", - "candeleterecord" => "1", - "chgstatus" => "1", - "imgtools" => "1", - "manage" => "1", - "modify_struct" => "1" - ]; - - $this->app->getAclForUser($user)->update_rights_to_base($base_id, $rights); - - return true; - } - - public static function mount_collection(Application $app, databox $databox, $coll_id, User $user) - { - - $sql = "INSERT INTO bas (base_id, active, server_coll_id, sbas_id, aliases, ord) - VALUES - (null, 1, :server_coll_id, :sbas_id, '', :ord)"; - $stmt = $databox->get_appbox()->get_connection()->prepare($sql); - $stmt->execute([ - ':server_coll_id' => $coll_id, - ':sbas_id' => $databox->get_sbas_id(), - ':ord' => self::getNewOrder($databox->get_appbox()->get_connection(), $databox->get_sbas_id()), - ]); - $stmt->closeCursor(); - - $new_bas = $databox->get_appbox()->get_connection()->lastInsertId(); - $databox->get_appbox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); - - $databox->delete_data_from_cache(databox::CACHE_COLLECTIONS); - - cache_databox::update($app, $databox->get_sbas_id(), 'structure'); - - phrasea::reset_baseDatas($databox->get_appbox()); - - $collection = self::get_from_base_id($app, $new_bas); - $collection->set_admin($new_bas, $user); - - $app['dispatcher']->dispatch( - CollectionEvents::MOUNTED, - new MountedEvent( - $collection - ) - ); - - return $new_bas; - } - - public static function getLogo($base_id, Application $app, $printname = false) - { - $base_id_key = $base_id . '_' . ($printname ? '1' : '0'); - - if ( ! isset(self::$_logos[$base_id_key])) { - - if (is_file($app['root.path'] . '/config/minilogos/' . $base_id)) { - $name = phrasea::bas_labels($base_id, $app); - self::$_logos[$base_id_key] = ''; - } elseif ($printname) { - self::$_logos[$base_id_key] = phrasea::bas_labels($base_id, $app); - } - } - - return isset(self::$_logos[$base_id_key]) ? self::$_logos[$base_id_key] : ''; - } - - public static function getWatermark($base_id) - { - if ( ! isset(self::$_watermarks['base_id'])) { - - if (is_file(__DIR__ . '/../../config/wm/' . $base_id)) - self::$_watermarks['base_id'] = ''; - } - - return isset(self::$_watermarks['base_id']) ? self::$_watermarks['base_id'] : ''; - } - - public static function getPresentation($base_id) - { - if ( ! isset(self::$_presentations['base_id'])) { - - if (is_file(__DIR__ . '/../../config/presentation/' . $base_id)) - self::$_presentations['base_id'] = ''; - } - - return isset(self::$_presentations['base_id']) ? self::$_presentations['base_id'] : ''; - } - - public static function getStamp($base_id) - { - if ( ! isset(self::$_stamps['base_id'])) { - - if (is_file(__DIR__ . '/../../config/stamp/' . $base_id)) - self::$_stamps['base_id'] = ''; - } - - return isset(self::$_stamps['base_id']) ? self::$_stamps['base_id'] : ''; - } - - public function get_cache_key($option = null) - { - return 'collection_' . $this->coll_id . ($option ? '_' . $option : ''); - } - - public function get_data_from_cache($option = null) - { - return $this->databox->get_data_from_cache($this->get_cache_key($option)); - } - - public function set_data_to_cache($value, $option = null, $duration = 0) - { - return $this->databox->set_data_to_cache($value, $this->get_cache_key($option), $duration); - } - - public function delete_data_from_cache($option = null) - { - return $this->databox->delete_data_from_cache($this->get_cache_key($option)); - } - - public static function purge() - { - self::$_collections = []; - } - /** * Tells whether registration is activated for provided collection or not. * @@ -877,7 +783,7 @@ class collection implements cache_cacheableInterface, ThumbnailedElement } foreach ($element as $caninscript) { - if (false !== (Boolean) (string) $caninscript) { + if (false !== (bool)(string)$caninscript) { return true; } } @@ -888,18 +794,47 @@ class collection implements cache_cacheableInterface, ThumbnailedElement /** * Gets terms of use. * - * @param \collection $collection - * * @return null|string */ public function getTermsOfUse() { if (false === $xml = simplexml_load_string($this->get_prefs())) { - return; + return null; } foreach ($xml->xpath('/baseprefs/cgu') as $sbpcgu) { return $sbpcgu->saveXML(); } } + + public function get_cache_key($option = null) + { + return 'collection_' . $this->collectionVO->getCollectionId() . ($option ? '_' . $option : ''); + } + + public function get_data_from_cache($option = null) + { + return $this->databox->get_data_from_cache($this->get_cache_key($option)); + } + + public function set_data_to_cache($value, $option = null, $duration = 0) + { + return $this->databox->set_data_to_cache($value, $this->get_cache_key($option), $duration); + } + + public function delete_data_from_cache($option = null) + { + $this->databox->delete_data_from_cache($this->get_cache_key($option)); + } + + /** + * @param Application $app + */ + private function fetchInternalServices(Application $app) + { + $this->app = $app; + $this->databox = $app->getApplicationBox()->get_databox($this->reference->getDataboxId()); + $this->collectionService = $app->getApplicationBox()->getCollectionService(); + $this->collectionRepositoryRegistry = $app['repo.collections-registry']; + } } diff --git a/lib/classes/databox.php b/lib/classes/databox.php index ecf510463d..94f07ff5ed 100644 --- a/lib/classes/databox.php +++ b/lib/classes/databox.php @@ -10,10 +10,12 @@ */ use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Collection\CollectionRepositoryRegistry; use Alchemy\Phrasea\Core\Connection\ConnectionSettings; use Alchemy\Phrasea\Core\PhraseaTokens; use Alchemy\Phrasea\Core\Thumbnail\ThumbnailedElement; use Alchemy\Phrasea\Core\Version\DataboxVersionRepository; +use Alchemy\Phrasea\Databox\DataboxRepository; use Alchemy\Phrasea\Databox\Record\RecordRepository; use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Model\Entities\User; @@ -46,6 +48,7 @@ class databox extends base implements ThumbnailedElement const CACHE_COLLECTIONS = 'collections'; const CACHE_STRUCTURE = 'structure'; const PIC_PDF = 'logopdf'; + const CACHE_CGUS = 'cgus'; /** @var array */ protected static $_xpath_thesaurus = []; @@ -53,9 +56,189 @@ class databox extends base implements ThumbnailedElement protected static $_dom_thesaurus = []; /** @var array */ protected static $_thesaurus = []; + /** @var SimpleXMLElement */ protected static $_sxml_thesaurus = []; + /** + * @param Application $app + * @param Connection $databoxConnection + * @param SplFileInfo $data_template + * @return databox + * @throws \Doctrine\DBAL\DBALException + */ + public static function create(Application $app, Connection $databoxConnection, \SplFileInfo $data_template) + { + if ( ! file_exists($data_template->getRealPath())) { + throw new \InvalidArgumentException($data_template->getRealPath() . " does not exist"); + } + + $host = $databoxConnection->getHost(); + $port = $databoxConnection->getPort(); + $dbname = $databoxConnection->getDatabase(); + $user = $databoxConnection->getUsername(); + $password = $databoxConnection->getPassword(); + + $appbox = $app->getApplicationBox(); + + try { + $sql = 'CREATE DATABASE `' . $dbname . '` CHARACTER SET utf8 COLLATE utf8_unicode_ci'; + $stmt = $databoxConnection->prepare($sql); + $stmt->execute(); + $stmt->closeCursor(); + } catch (\Exception $e) { + + } + + $sql = 'USE `' . $dbname . '`'; + $stmt = $databoxConnection->prepare($sql); + $stmt->execute(); + $stmt->closeCursor(); + + $app['orm.add']([ + 'host' => $host, + 'port' => $port, + 'dbname' => $dbname, + 'user' => $user, + 'password' => $password + ]); + + phrasea::reset_sbasDatas($app['phraseanet.appbox']); + + /** @var DataboxRepository $databoxRepository */ + $databoxRepository = $app['repo.databoxes']; + $databox = $databoxRepository->create($host, $port, $user, $password, $dbname); + + $appbox->delete_data_from_cache(appbox::CACHE_LIST_BASES); + + $databox->insert_datas(); + $databox->setNewStructure( + $data_template, $app['conf']->get(['main', 'storage', 'subdefs']) + ); + + $app['dispatcher']->dispatch(DataboxEvents::CREATED, new CreatedEvent($databox)); + + return $databox; + } + + /** + * + * @param Application $app + * @param string $host + * @param int $port + * @param string $user + * @param string $password + * @param string $dbname + * @return databox + */ + public static function mount(Application $app, $host, $port, $user, $password, $dbname) + { + $app['db.provider']([ + 'host' => $host, + 'port' => $port, + 'user' => $user, + 'password' => $password, + 'dbname' => $dbname, + ])->connect(); + + /** @var DataboxRepository $databoxRepository */ + $databoxRepository = $app['repo.databoxes']; + $databox = $databoxRepository->mount($host, $port, $user, $password, $dbname); + + $databox->delete_data_from_cache(databox::CACHE_COLLECTIONS); + $app->getApplicationBox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); + + phrasea::reset_sbasDatas($app['phraseanet.appbox']); + cache_databox::update($app, $databox->get_sbas_id(), 'structure'); + + $app['dispatcher']->dispatch(DataboxEvents::MOUNTED, new MountedEvent($databox)); + + return $databox; + } + + public static function get_available_dcfields() + { + return [ + databox_Field_DCESAbstract::Contributor => new databox_Field_DCES_Contributor(), + databox_Field_DCESAbstract::Coverage => new databox_Field_DCES_Coverage(), + databox_Field_DCESAbstract::Creator => new databox_Field_DCES_Creator(), + databox_Field_DCESAbstract::Date => new databox_Field_DCES_Date(), + databox_Field_DCESAbstract::Description => new databox_Field_DCES_Description(), + databox_Field_DCESAbstract::Format => new databox_Field_DCES_Format(), + databox_Field_DCESAbstract::Identifier => new databox_Field_DCES_Identifier(), + databox_Field_DCESAbstract::Language => new databox_Field_DCES_Language(), + databox_Field_DCESAbstract::Publisher => new databox_Field_DCES_Publisher(), + databox_Field_DCESAbstract::Relation => new databox_Field_DCES_Relation(), + databox_Field_DCESAbstract::Rights => new databox_Field_DCES_Rights(), + databox_Field_DCESAbstract::Source => new databox_Field_DCES_Source(), + databox_Field_DCESAbstract::Subject => new databox_Field_DCES_Subject(), + databox_Field_DCESAbstract::Title => new databox_Field_DCES_Title(), + databox_Field_DCESAbstract::Type => new databox_Field_DCES_Type() + ]; + } + + /** + * + * @param int $sbas_id + * @return string + */ + public static function getPrintLogo($sbas_id) + { + $out = ''; + if (is_file(($filename = __DIR__ . '/../../config/minilogos/'.\databox::PIC_PDF.'_' . $sbas_id . '.jpg'))) + $out = file_get_contents($filename); + + return $out; + } + + /** + * @param TranslatorInterface $translator + * @param string $structure + * @return Array + */ + public static function get_structure_errors(TranslatorInterface $translator, $structure) + { + $sx_structure = simplexml_load_string($structure); + + $subdefgroup = $sx_structure->subdefs[0]; + $AvSubdefs = []; + + $errors = []; + + foreach ($subdefgroup as $k => $subdefs) { + $subdefgroup_name = trim((string) $subdefs->attributes()->name); + + if ($subdefgroup_name == '') { + $errors[] = $translator->trans('ERREUR : TOUTES LES BALISES subdefgroup necessitent un attribut name'); + continue; + } + + if ( ! isset($AvSubdefs[$subdefgroup_name])) + $AvSubdefs[$subdefgroup_name] = []; + + foreach ($subdefs as $sd) { + $sd_name = trim(mb_strtolower((string) $sd->attributes()->name)); + $sd_class = trim(mb_strtolower((string) $sd->attributes()->class)); + if ($sd_name == '' || isset($AvSubdefs[$subdefgroup_name][$sd_name])) { + $errors[] = $translator->trans('ERREUR : Les name de subdef sont uniques par groupe de subdefs et necessaire'); + continue; + } + if ( ! in_array($sd_class, ['thumbnail', 'preview', 'document'])) { + $errors[] = $translator->trans('ERREUR : La classe de subdef est necessaire et egal a "thumbnail","preview" ou "document"'); + continue; + } + $AvSubdefs[$subdefgroup_name][$sd_name] = $sd; + } + } + + return $errors; + } + + public static function purge() + { + self::$_xpath_thesaurus = self::$_dom_thesaurus = self::$_thesaurus = self::$_sxml_thesaurus = []; + } + /** @var int */ protected $id; /** @var string */ @@ -72,24 +255,32 @@ class databox extends base implements ThumbnailedElement protected $meta_struct; /** @var databox_subdefsStructure */ protected $subdef_struct; - + protected $thesaurus; + protected $cterms; + protected $cgus; + /** @var DataboxRepository */ + private $databoxRepository; /** @var RecordRepository */ private $recordRepository; /** @var string[] */ private $labels = []; + /** @var int */ private $ord; + /** @var string */ private $viewname; /** * @param Application $app - * @param int $sbas_id - * @param array $row + * @param int $sbas_id + * @param DataboxRepository $databoxRepository + * @param array $row */ - public function __construct(Application $app, $sbas_id, array $row) + public function __construct(Application $app, $sbas_id, DataboxRepository $databoxRepository, array $row) { assert(is_int($sbas_id)); assert($sbas_id > 0); + $this->databoxRepository = $databoxRepository; $this->id = $sbas_id; $connectionConfigs = phrasea::sbas_params($app); @@ -116,6 +307,212 @@ class databox extends base implements ThumbnailedElement $this->loadFromRow($row); } + public function setNewStructure(\SplFileInfo $data_template, $path_doc) + { + if ( ! file_exists($data_template->getPathname())) { + throw new \InvalidArgumentException(sprintf('File %s does not exists')); + } + + $contents = file_get_contents($data_template->getPathname()); + + $contents = str_replace( + ["{{basename}}", "{{datapathnoweb}}"] + , [$this->connectionSettings->getDatabaseName(), rtrim($path_doc, '/').'/'] + , $contents + ); + + $dom_doc = new DOMDocument(); + $dom_doc->loadXML($contents); + $this->saveStructure($dom_doc); + + $this->feed_meta_fields(); + + return $this; + } + + /** + * + * @param DOMDocument $dom_struct + * @return databox + */ + public function saveStructure(DOMDocument $dom_struct) + { + $old_structure = $this->get_dom_structure(); + + $dom_struct->documentElement + ->setAttribute("modification_date", $now = date("YmdHis")); + + $sql = "UPDATE pref SET value= :structure, updated_on= :now WHERE prop='structure'"; + + $this->structure = $dom_struct->saveXML(); + + $stmt = $this->get_connection()->prepare($sql); + $stmt->execute( + [ + ':structure' => $this->structure, + ':now' => $now + ] + ); + $stmt->closeCursor(); + + $this->_sxml_structure = $this->_dom_structure = $this->_xpath_structure = null; + + $this->meta_struct = null; + + $this->get_appbox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); + $this->delete_data_from_cache(self::CACHE_STRUCTURE); + $this->delete_data_from_cache(self::CACHE_META_STRUCT); + + cache_databox::update($this->app, $this->id, 'structure'); + + $this->databoxRepository->save($this); + + $this->app['dispatcher']->dispatch( + DataboxEvents::STRUCTURE_CHANGED, + new StructureChangedEvent( + $this, + array( + 'dom_before'=>$old_structure + ) + ) + ); + + return $this; + } + + /** + * @return DOMDocument + */ + public function get_dom_structure() + { + if ($this->_dom_structure) { + return $this->_dom_structure; + } + + $structure = $this->get_structure(); + + $dom = new DOMDocument(); + + $dom->standalone = true; + $dom->preserveWhiteSpace = false; + $dom->formatOutput = true; + + if ($structure && $dom->loadXML($structure) !== false) + $this->_dom_structure = $dom; + else + $this->_dom_structure = false; + + return $this->_dom_structure; + } + + /** + * @return string + */ + public function get_structure() + { + if ($this->structure) { + return $this->structure; + } + + $this->structure = $this->retrieve_structure(); + + return $this->structure; + } + + public function feed_meta_fields() + { + $sxe = $this->get_sxml_structure(); + + foreach ($sxe->description->children() as $fname => $field) { + $dom_struct = $this->get_dom_structure(); + $xp_struct = $this->get_xpath_structure(); + $fname = (string) $fname; + $src = trim(isset($field['src']) ? str_replace('/rdf:RDF/rdf:Description/', '', $field['src']) : ''); + + $meta_id = isset($field['meta_id']) ? $field['meta_id'] : null; + if ( ! is_null($meta_id)) + continue; + + $nodes = $xp_struct->query('/record/description/' . $fname); + if ($nodes->length > 0) { + $nodes->item(0)->parentNode->removeChild($nodes->item(0)); + } + $this->saveStructure($dom_struct); + + $type = isset($field['type']) ? $field['type'] : 'string'; + $type = in_array($type + , [ + databox_field::TYPE_DATE + , databox_field::TYPE_NUMBER + , databox_field::TYPE_STRING + , databox_field::TYPE_TEXT + ] + ) ? $type : databox_field::TYPE_STRING; + + $multi = isset($field['multi']) ? (Boolean) (string) $field['multi'] : false; + + $meta_struct_field = databox_field::create($this->app, $this, $fname, $multi); + $meta_struct_field + ->set_readonly(isset($field['readonly']) ? (string) $field['readonly'] : 0) + ->set_indexable(isset($field['index']) ? (string) $field['index'] : '1') + ->set_separator(isset($field['separator']) ? (string) $field['separator'] : '') + ->set_required((isset($field['required']) && (string) $field['required'] == 1)) + ->set_business((isset($field['business']) && (string) $field['business'] == 1)) + ->set_aggregable((isset($field['aggregable']) ? (string) $field['aggregable'] : 0)) + ->set_type($type) + ->set_tbranch(isset($field['tbranch']) ? (string) $field['tbranch'] : '') + ->set_thumbtitle(isset($field['thumbtitle']) ? (string) $field['thumbtitle'] : (isset($field['thumbTitle']) ? $field['thumbTitle'] : '0')) + ->set_report(isset($field['report']) ? (string) $field['report'] : '1') + ->save(); + + try { + $meta_struct_field->set_tag(\databox_field::loadClassFromTagName($src))->save(); + } catch (\Exception $e) { + } + } + + return $this; + } + + /** + * + * @return SimpleXMLElement + */ + public function get_sxml_structure() + { + if ($this->_sxml_structure) { + return $this->_sxml_structure; + } + + $structure = $this->get_structure(); + + if ($structure && false !== $tmp = simplexml_load_string($structure)) + $this->_sxml_structure = $tmp; + else + $this->_sxml_structure = false; + + return $this->_sxml_structure; + } + + /** + * @return DOMXpath + */ + public function get_xpath_structure() + { + if ($this->_xpath_structure) { + return $this->_xpath_structure; + } + + $dom_doc = $this->get_dom_structure(); + + if ($dom_doc && ($tmp = new DOMXpath($dom_doc)) !== false) + $this->_xpath_structure = $tmp; + else + $this->_xpath_structure = false; + + return $this->_xpath_structure; + } + /** * @return RecordRepository */ @@ -128,118 +525,79 @@ class databox extends base implements ThumbnailedElement return $this->recordRepository; } - public function get_viewname() - { - return $this->viewname ? : $this->connectionSettings->getDatabaseName(); - } - - public function set_viewname($viewname) - { - $sql = 'UPDATE sbas SET viewname = :viewname WHERE sbas_id = :sbas_id'; - - $stmt = $this->get_appbox()->get_connection()->prepare($sql); - $stmt->execute([':viewname' => $viewname, ':sbas_id' => $this->id]); - $stmt->closeCursor(); - - $this->get_appbox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); - cache_databox::update($this->app, $this->id, 'structure'); - - $this->viewname = $viewname; - - return $this; - } - public function get_ord() { return $this->ord; } - /** - * @return appbox - */ - public function get_appbox() - { - return $this->app->getApplicationBox(); - } - public function getRootIdentifier() { return $this->get_sbas_id(); } + /** + * Returns current sbas_id + * + * @return int + */ + public function get_sbas_id() + { + return $this->id; + } + public function updateThumbnail($thumbnailType, File $file = null) { $this->delete_data_from_cache('printLogo'); } + public function delete_data_from_cache($option = null) + { + switch ($option) { + case self::CACHE_CGUS: + $this->cgus = null; + break; + case self::CACHE_META_STRUCT: + $this->meta_struct = null; + break; + case self::CACHE_STRUCTURE: + $this->_dom_structure = $this->_xpath_structure = $this->structure = $this->_sxml_structure = null; + break; + case self::CACHE_THESAURUS: + $this->thesaurus = null; + unset(self::$_dom_thesaurus[$this->id]); + break; + default: + break; + } + parent::delete_data_from_cache($option); + } + + /** + * @return int[] + */ + public function get_collection_unique_ids() + { + $collectionsIds = []; + + foreach ($this->get_collections() as $collection) { + $collectionsIds[] = $collection->get_base_id(); + } + + return $collectionsIds; + } + /** * @return collection[] */ public function get_collections() { - $ret = []; + /** @var CollectionRepositoryRegistry $repositoryRegistry */ + $repositoryRegistry = $this->app['repo.collections-registry']; + $repository = $repositoryRegistry->getRepositoryByDatabox($this->get_sbas_id()); - foreach ($this->get_available_collections() as $coll_id) { - $ret[] = collection::get_from_coll_id($this->app, $this, $coll_id); - } - - return $ret; - } - - public function get_collection_unique_ids() - { - static $base_ids_cache = []; - - if (isset($base_ids_cache[$this->id])) { - return $base_ids_cache[$this->id]; - } - - $conn = $this->get_appbox()->get_connection(); - $sql = "SELECT b.base_id FROM bas b WHERE b.sbas_id = :sbas_id AND b.active = '1' ORDER BY b.ord ASC"; - $stmt = $conn->prepare($sql); - $stmt->execute([':sbas_id' => $this->id]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $base_ids = []; - foreach ($rs as $row) { - $base_ids[] = (int) $row['base_id']; - } - - return $base_ids_cache[$this->id] = $base_ids; - } - - protected function get_available_collections() - { - try { - $data = $this->get_data_from_cache(self::CACHE_COLLECTIONS); - if (is_array($data)) { - return $data; - } - } catch (\Exception $e) { - - } - - $conn = $this->get_appbox()->get_connection(); - - $sql = "SELECT b.server_coll_id FROM sbas s, bas b - WHERE s.sbas_id = b.sbas_id AND b.sbas_id = :sbas_id - AND b.active = '1' - ORDER BY s.ord ASC, b.ord,b.base_id ASC"; - $stmt = $conn->prepare($sql); - $stmt->execute([':sbas_id' => $this->id]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $ret = []; - - foreach ($rs as $row) { - $ret[] = (int) $row['server_coll_id']; - } - - $this->set_data_to_cache($ret, self::CACHE_COLLECTIONS); - - return $ret; + return array_filter($repository->findAll(), function (collection $collection) { + return $collection->is_active(); + }); } /** @@ -266,6 +624,36 @@ class databox extends base implements ThumbnailedElement } } + public function get_viewname() + { + return $this->viewname ? : $this->connectionSettings->getDatabaseName(); + } + + public function set_viewname($viewname) + { + $sql = 'UPDATE sbas SET viewname = :viewname WHERE sbas_id = :sbas_id'; + + $stmt = $this->get_appbox()->get_connection()->prepare($sql); + $stmt->execute([':viewname' => $viewname, ':sbas_id' => $this->id]); + $stmt->closeCursor(); + + $this->get_appbox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); + cache_databox::update($this->app, $this->id, 'structure'); + + $this->viewname = $viewname; + $this->databoxRepository->save($this); + + return $this; + } + + /** + * @return appbox + */ + public function get_appbox() + { + return $this->app->getApplicationBox(); + } + public function set_label($code, $label) { if (!array_key_exists($code, $this->labels)) { @@ -280,6 +668,8 @@ class databox extends base implements ThumbnailedElement $this->labels[$code] = $label; + $this->databoxRepository->save($this); + phrasea::reset_sbasDatas($this->app['phraseanet.appbox']); return $this; @@ -292,17 +682,8 @@ class databox extends base implements ThumbnailedElement { /** @var StatusStructureFactory $structureFactory */ $structureFactory = $this->app['factory.status-structure']; - return $structureFactory->getStructure($this); - } - /** - * Returns current sbas_id - * - * @return int - */ - public function get_sbas_id() - { - return $this->id; + return $structureFactory->getStructure($this); } public function get_record_details($sort) @@ -415,7 +796,7 @@ class databox extends base implements ThumbnailedElement $old_dbname = $this->get_dbname(); foreach ($this->get_collections() as $collection) { - $collection->unmount_collection($this->app); + $collection->unmount(); } $query = $this->app['phraseanet.user-query']; @@ -468,6 +849,7 @@ class databox extends base implements ThumbnailedElement $stmt->execute([':sbas_id' => $this->id]); $stmt->closeCursor(); + $this->databoxRepository->unmount($this); $this->get_appbox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); $this->app['dispatcher']->dispatch( @@ -483,176 +865,6 @@ class databox extends base implements ThumbnailedElement return; } - /** - * @param Application $app - * @param Connection $connection - * @param SplFileInfo $data_template - * @return databox - * @throws \Doctrine\DBAL\DBALException - */ - public static function create(Application $app, Connection $connection, \SplFileInfo $data_template) - { - if ( ! file_exists($data_template->getRealPath())) { - throw new \InvalidArgumentException($data_template->getRealPath() . " does not exist"); - } - - $sql = 'SELECT sbas_id - FROM sbas - WHERE host = :host AND port = :port AND dbname = :dbname - AND user = :user AND pwd = :password'; - - $host = $connection->getHost(); - $port = $connection->getPort(); - $dbname = $connection->getDatabase(); - $user = $connection->getUsername(); - $password = $connection->getPassword(); - - $params = [ - ':host' => $host, - ':port' => $port, - ':dbname' => $dbname, - ':user' => $user, - ':password' => $password - ]; - - /** @var appbox $appbox */ - $appbox = $app['phraseanet.appbox']; - $stmt = $appbox->get_connection()->prepare($sql); - $stmt->execute($params); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if ($row) { - return $appbox->get_databox((int) $row['sbas_id']); - } - - try { - $sql = 'CREATE DATABASE `' . $dbname . '` - CHARACTER SET utf8 COLLATE utf8_unicode_ci'; - $stmt = $connection->prepare($sql); - $stmt->execute(); - $stmt->closeCursor(); - } catch (\Exception $e) { - - } - - $sql = 'USE `' . $dbname . '`'; - $stmt = $connection->prepare($sql); - $stmt->execute(); - $stmt->closeCursor(); - - $sql = 'SELECT MAX(ord) as ord FROM sbas'; - $stmt = $appbox->get_connection()->prepare($sql); - $stmt->execute(); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if ($row) { - $ord = $row['ord'] + 1; - } - - $params[':ord'] = $ord; - - $sql = 'INSERT INTO sbas (sbas_id, ord, host, port, dbname, sqlengine, user, pwd) - VALUES (null, :ord, :host, :port, :dbname, "MYSQL", :user, :password)'; - $stmt = $appbox->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - $sbas_id = (int) $appbox->get_connection()->lastInsertId(); - - $app['orm.add']([ - 'host' => $host, - 'port' => $port, - 'dbname' => $dbname, - 'user' => $user, - 'password' => $password - ]); - - $appbox->delete_data_from_cache(appbox::CACHE_LIST_BASES); - - $databox = $appbox->get_databox($sbas_id); - $databox->insert_datas(); - - $databox->setNewStructure( - $data_template, $app['conf']->get(['main', 'storage', 'subdefs']) - ); - - $app['dispatcher']->dispatch( - DataboxEvents::CREATED, - new CreatedEvent( - $databox - ) - ); - - return $databox; - } - - /** - * - * @param Application $app - * @param string $host - * @param int $port - * @param string $user - * @param string $password - * @param string $dbname - * @return databox - */ - public static function mount(Application $app, $host, $port, $user, $password, $dbname) - { - $conn = $app['db.provider']([ - 'host' => $host, - 'port' => $port, - 'user' => $user, - 'password' => $password, - 'dbname' => $dbname, - ]); - - $conn->connect(); - - $conn = $app->getApplicationBox()->get_connection(); - $sql = 'SELECT MAX(ord) as ord FROM sbas'; - $stmt = $conn->prepare($sql); - $stmt->execute(); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - if ($row) - $ord = $row['ord'] + 1; - - $sql = 'INSERT INTO sbas (sbas_id, ord, host, port, dbname, sqlengine, user, pwd) - VALUES (null, :ord, :host, :port, :dbname, "MYSQL", :user, :password)'; - $stmt = $conn->prepare($sql); - $stmt->execute([ - ':ord' => $ord, - ':host' => $host, - ':port' => $port, - ':dbname' => $dbname, - ':user' => $user, - ':password' => $password - ]); - - $stmt->closeCursor(); - $sbas_id = (int) $conn->lastInsertId(); - - $app->getApplicationBox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); - - $databox = $app->findDataboxById($sbas_id); - - $databox->delete_data_from_cache(databox::CACHE_COLLECTIONS); - - phrasea::reset_sbasDatas($app['phraseanet.appbox']); - - cache_databox::update($app, $databox->get_sbas_id(), 'structure'); - - $app['dispatcher']->dispatch( - DataboxEvents::MOUNTED, - new MountedEvent( - $databox - ) - ); - - return $databox; - } - public function get_base_type() { return self::BASE_TYPE; @@ -681,8 +893,7 @@ class databox extends base implements ThumbnailedElement } /** - * - * @return databox_subdefsStructure + * @return databox_subdefsStructure|databox_subdef[][] */ public function get_subdef_structure() { @@ -693,30 +904,6 @@ class databox extends base implements ThumbnailedElement return $this->subdef_struct; } - public static function dispatch(Filesystem $filesystem, $repository_path, $date = false) - { - if (! $date) { - $date = date('Y-m-d H:i:s'); - } - - $repository_path = p4string::addEndSlash($repository_path); - - $year = date('Y', strtotime($date)); - $month = date('m', strtotime($date)); - $day = date('d', strtotime($date)); - - $n = 0; - $comp = $year . DIRECTORY_SEPARATOR . $month . DIRECTORY_SEPARATOR . $day . DIRECTORY_SEPARATOR; - - while (($pathout = $repository_path . $comp . self::addZeros($n)) && is_dir($pathout) && iterator_count(new \DirectoryIterator($pathout)) > 100) { - $n ++; - } - - $filesystem->mkdir($pathout, 0750); - - return $pathout . DIRECTORY_SEPARATOR; - } - public function delete() { $old_dbname = $this->get_dbname(); @@ -728,6 +915,8 @@ class databox extends base implements ThumbnailedElement $this->get_appbox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); + $this->databoxRepository->delete($this); + $this->app['dispatcher']->dispatch( DataboxEvents::DELETED, new DeletedEvent( @@ -741,11 +930,6 @@ class databox extends base implements ThumbnailedElement return; } - private static function addZeros($n, $length = 5) - { - return str_pad($n, $length, '0'); - } - public function get_serialized_server_info() { return sprintf("%s@%s:%s (MySQL %s)", @@ -756,27 +940,6 @@ class databox extends base implements ThumbnailedElement ); } - public static function get_available_dcfields() - { - return [ - databox_Field_DCESAbstract::Contributor => new databox_Field_DCES_Contributor(), - databox_Field_DCESAbstract::Coverage => new databox_Field_DCES_Coverage(), - databox_Field_DCESAbstract::Creator => new databox_Field_DCES_Creator(), - databox_Field_DCESAbstract::Date => new databox_Field_DCES_Date(), - databox_Field_DCESAbstract::Description => new databox_Field_DCES_Description(), - databox_Field_DCESAbstract::Format => new databox_Field_DCES_Format(), - databox_Field_DCESAbstract::Identifier => new databox_Field_DCES_Identifier(), - databox_Field_DCESAbstract::Language => new databox_Field_DCES_Language(), - databox_Field_DCESAbstract::Publisher => new databox_Field_DCES_Publisher(), - databox_Field_DCESAbstract::Relation => new databox_Field_DCES_Relation(), - databox_Field_DCESAbstract::Rights => new databox_Field_DCES_Rights(), - databox_Field_DCESAbstract::Source => new databox_Field_DCES_Source(), - databox_Field_DCESAbstract::Subject => new databox_Field_DCES_Subject(), - databox_Field_DCESAbstract::Title => new databox_Field_DCES_Title(), - databox_Field_DCESAbstract::Type => new databox_Field_DCES_Type() - ]; - } - /** * * @return Array @@ -843,54 +1006,6 @@ class databox extends base implements ThumbnailedElement return $base_ids; } - /** - * - * @param DOMDocument $dom_struct - * @return databox - */ - public function saveStructure(DOMDocument $dom_struct) - { - $old_structure = $this->get_dom_structure(); - - $dom_struct->documentElement - ->setAttribute("modification_date", $now = date("YmdHis")); - - $sql = "UPDATE pref SET value= :structure, updated_on= :now WHERE prop='structure'"; - - $this->structure = $dom_struct->saveXML(); - - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute( - [ - ':structure' => $this->structure, - ':now' => $now - ] - ); - $stmt->closeCursor(); - - $this->_sxml_structure = $this->_dom_structure = $this->_xpath_structure = null; - - $this->meta_struct = null; - - $this->get_appbox()->delete_data_from_cache(appbox::CACHE_LIST_BASES); - $this->delete_data_from_cache(self::CACHE_STRUCTURE); - $this->delete_data_from_cache(self::CACHE_META_STRUCT); - - cache_databox::update($this->app, $this->id, 'structure'); - - $this->app['dispatcher']->dispatch( - DataboxEvents::STRUCTURE_CHANGED, - new StructureChangedEvent( - $this, - array( - 'dom_before'=>$old_structure - ) - ) - ); - - return $this; - } - public function saveCterms(DOMDocument $dom_cterms) { $dom_cterms->documentElement->setAttribute("modification_date", $now = date("YmdHis")); @@ -908,9 +1023,10 @@ class databox extends base implements ThumbnailedElement $stmt->execute($params); $stmt->closeCursor(); + $this->databoxRepository->save($this); + return $this; } - protected $thesaurus; public function saveThesaurus(DOMDocument $dom_thesaurus) { @@ -925,6 +1041,8 @@ class databox extends base implements ThumbnailedElement $stmt->closeCursor(); $this->delete_data_from_cache(databox::CACHE_THESAURUS); + $this->databoxRepository->save($this); + $this->app['dispatcher']->dispatch( DataboxEvents::THESAURUS_CHANGED, new ThesaurusChangedEvent( @@ -938,82 +1056,56 @@ class databox extends base implements ThumbnailedElement return $this; } - public function setNewStructure(\SplFileInfo $data_template, $path_doc) + /** + * @return DOMDocument + */ + public function get_dom_thesaurus() { - if ( ! file_exists($data_template->getPathname())) { - throw new \InvalidArgumentException(sprintf('File %s does not exists')); + $sbas_id = $this->id; + if (isset(self::$_dom_thesaurus[$sbas_id])) { + return self::$_dom_thesaurus[$sbas_id]; } - $contents = file_get_contents($data_template->getPathname()); + $thesaurus = $this->get_thesaurus(); - $contents = str_replace( - ["{{basename}}", "{{datapathnoweb}}"] - , [$this->connectionSettings->getDatabaseName(), rtrim($path_doc, '/').'/'] - , $contents - ); + $dom = new DOMDocument(); - $dom_doc = new DOMDocument(); - $dom_doc->loadXML($contents); - $this->saveStructure($dom_doc); + if ($thesaurus && false !== $dom->loadXML($thesaurus)) { + self::$_dom_thesaurus[$sbas_id] = $dom; + } else { + self::$_dom_thesaurus[$sbas_id] = false; + unset($dom); + } - $this->feed_meta_fields(); - - return $this; + return self::$_dom_thesaurus[$sbas_id]; } - public function feed_meta_fields() + /** + * @return string + */ + public function get_thesaurus() { - $sxe = $this->get_sxml_structure(); + try { + $this->thesaurus = $this->get_data_from_cache(self::CACHE_THESAURUS); - foreach ($sxe->description->children() as $fname => $field) { - $dom_struct = $this->get_dom_structure(); - $xp_struct = $this->get_xpath_structure(); - $fname = (string) $fname; - $src = trim(isset($field['src']) ? str_replace('/rdf:RDF/rdf:Description/', '', $field['src']) : ''); - - $meta_id = isset($field['meta_id']) ? $field['meta_id'] : null; - if ( ! is_null($meta_id)) - continue; - - $nodes = $xp_struct->query('/record/description/' . $fname); - if ($nodes->length > 0) { - $nodes->item(0)->parentNode->removeChild($nodes->item(0)); - } - $this->saveStructure($dom_struct); - - $type = isset($field['type']) ? $field['type'] : 'string'; - $type = in_array($type - , [ - databox_field::TYPE_DATE - , databox_field::TYPE_NUMBER - , databox_field::TYPE_STRING - , databox_field::TYPE_TEXT - ] - ) ? $type : databox_field::TYPE_STRING; - - $multi = isset($field['multi']) ? (Boolean) (string) $field['multi'] : false; - - $meta_struct_field = databox_field::create($this->app, $this, $fname, $multi); - $meta_struct_field - ->set_readonly(isset($field['readonly']) ? (string) $field['readonly'] : 0) - ->set_indexable(isset($field['index']) ? (string) $field['index'] : '1') - ->set_separator(isset($field['separator']) ? (string) $field['separator'] : '') - ->set_required((isset($field['required']) && (string) $field['required'] == 1)) - ->set_business((isset($field['business']) && (string) $field['business'] == 1)) - ->set_aggregable((isset($field['aggregable']) ? (string) $field['aggregable'] : 0)) - ->set_type($type) - ->set_tbranch(isset($field['tbranch']) ? (string) $field['tbranch'] : '') - ->set_thumbtitle(isset($field['thumbtitle']) ? (string) $field['thumbtitle'] : (isset($field['thumbTitle']) ? $field['thumbTitle'] : '0')) - ->set_report(isset($field['report']) ? (string) $field['report'] : '1') - ->save(); - - try { - $meta_struct_field->set_tag(\databox_field::loadClassFromTagName($src))->save(); - } catch (\Exception $e) { - } + return $this->thesaurus; + } catch (\Exception $e) { + unset($e); } - return $this; + try { + $sql = 'SELECT value AS thesaurus FROM pref WHERE prop="thesaurus" LIMIT 1;'; + $stmt = $this->get_connection()->prepare($sql); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt->closeCursor(); + $this->thesaurus = $row['thesaurus']; + $this->set_data_to_cache($this->thesaurus, self::CACHE_THESAURUS); + } catch (\Exception $e) { + unset($e); + } + + return $this->thesaurus; } /** @@ -1075,20 +1167,6 @@ class databox extends base implements ThumbnailedElement return $this; } - /** - * - * @param int $sbas_id - * @return string - */ - public static function getPrintLogo($sbas_id) - { - $out = ''; - if (is_file(($filename = __DIR__ . '/../../config/minilogos/'.\databox::PIC_PDF.'_' . $sbas_id . '.jpg'))) - $out = file_get_contents($filename); - - return $out; - } - public function clear_logs() { foreach (['log', 'log_colls', 'log_docs', 'log_search', 'log_view', 'log_thumb'] as $table) { @@ -1118,30 +1196,6 @@ class databox extends base implements ThumbnailedElement return $this; } - /** - * @return DOMDocument - */ - public function get_dom_thesaurus() - { - $sbas_id = $this->id; - if (isset(self::$_dom_thesaurus[$sbas_id])) { - return self::$_dom_thesaurus[$sbas_id]; - } - - $thesaurus = $this->get_thesaurus(); - - $dom = new DOMDocument(); - - if ($thesaurus && false !== $dom->loadXML($thesaurus)) { - self::$_dom_thesaurus[$sbas_id] = $dom; - } else { - self::$_dom_thesaurus[$sbas_id] = false; - unset($dom); - } - - return self::$_dom_thesaurus[$sbas_id]; - } - /** * @return DOMXpath */ @@ -1182,121 +1236,6 @@ class databox extends base implements ThumbnailedElement return self::$_sxml_thesaurus[$sbas_id]; } - /** - * @return string - */ - public function get_thesaurus() - { - try { - $this->thesaurus = $this->get_data_from_cache(self::CACHE_THESAURUS); - - return $this->thesaurus; - } catch (\Exception $e) { - unset($e); - } - - try { - $sql = 'SELECT value AS thesaurus FROM pref WHERE prop="thesaurus" LIMIT 1;'; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute(); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - $this->thesaurus = $row['thesaurus']; - $this->set_data_to_cache($this->thesaurus, self::CACHE_THESAURUS); - } catch (\Exception $e) { - unset($e); - } - - return $this->thesaurus; - } - - /** - * @return string - */ - public function get_structure() - { - if ($this->structure) { - return $this->structure; - } - - $this->structure = $this->retrieve_structure(); - - return $this->structure; - } - - protected function retrieve_structure() - { - try { - $data = $this->get_data_from_cache(self::CACHE_STRUCTURE); - if (is_array($data)) { - return $data; - } - } catch (\Exception $e) { - - } - - $structure = null; - $sql = "SELECT value FROM pref WHERE prop='structure'"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute(); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if ($row) - $structure = $row['value']; - $this->set_data_to_cache($structure, self::CACHE_STRUCTURE); - - return $structure; - } - protected $cterms; - - /** - * - * @return string - */ - public function get_cterms() - { - if ($this->cterms) { - return $this->cterms; - } - - $sql = "SELECT value FROM pref WHERE prop='cterms'"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute(); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if ($row) - $this->cterms = $row['value']; - - return $this->cterms; - } - - /** - * @return DOMDocument - */ - public function get_dom_structure() - { - if ($this->_dom_structure) { - return $this->_dom_structure; - } - - $structure = $this->get_structure(); - - $dom = new DOMDocument(); - - $dom->standalone = true; - $dom->preserveWhiteSpace = false; - $dom->formatOutput = true; - - if ($structure && $dom->loadXML($structure) !== false) - $this->_dom_structure = $dom; - else - $this->_dom_structure = false; - - return $this->_dom_structure; - } - /** * @return DOMDocument */ @@ -1324,87 +1263,57 @@ class databox extends base implements ThumbnailedElement /** * - * @return SimpleXMLElement + * @return string */ - public function get_sxml_structure() + public function get_cterms() { - if ($this->_sxml_structure) { - return $this->_sxml_structure; + if ($this->cterms) { + return $this->cterms; } - $structure = $this->get_structure(); + $sql = "SELECT value FROM pref WHERE prop='cterms'"; + $stmt = $this->get_connection()->prepare($sql); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt->closeCursor(); - if ($structure && false !== $tmp = simplexml_load_string($structure)) - $this->_sxml_structure = $tmp; - else - $this->_sxml_structure = false; + if ($row) + $this->cterms = $row['value']; - return $this->_sxml_structure; + return $this->cterms; } - /** - * @return DOMXpath - */ - public function get_xpath_structure() + public function update_cgus($locale, $terms, $reset_date) { - if ($this->_xpath_structure) { - return $this->_xpath_structure; - } + $old_tou = $this->get_cgus(); - $dom_doc = $this->get_dom_structure(); + $terms = str_replace(["\r\n", "\n", "\r"], ['', '', ''], strip_tags($terms, '

+ @@ -128,7 +126,7 @@
{{ 'setup::Tests d\'envois d\'emails' | trans }}
- +
diff --git a/templates/web/admin/plugins/index.html.twig b/templates/web/admin/plugins/index.html.twig index cc6877f71a..c24865c5ff 100644 --- a/templates/web/admin/plugins/index.html.twig +++ b/templates/web/admin/plugins/index.html.twig @@ -3,14 +3,14 @@ {% block content %}
{% for pluginName in plugins.keys() %}
  • - {{ 'plugin.name'|trans([], plugins[pluginName].localeTextDomain) }} + {{ 'plugin_name'|trans([], plugins[pluginName].localeTextDomain) }}
  • {% endfor %} diff --git a/templates/web/admin/plugins/show_fallback.html.twig b/templates/web/admin/plugins/show_fallback.html.twig index cbdaa5668b..892579d26b 100644 --- a/templates/web/admin/plugins/show_fallback.html.twig +++ b/templates/web/admin/plugins/show_fallback.html.twig @@ -3,7 +3,7 @@ {% block content %}
    diff --git a/templates/web/admin/publications/fiche.html.twig b/templates/web/admin/publications/fiche.html.twig index 0d7f7ab51f..d53d912280 100644 --- a/templates/web/admin/publications/fiche.html.twig +++ b/templates/web/admin/publications/fiche.html.twig @@ -33,31 +33,36 @@ sequentialUploads: true, add: function (e, data) { $('#upload-error').empty(); - $.each(data.files, function (index, file) { - var fileType = /^image\/(gif|jpeg|png)$/; - if(typeof loadImage == 'function' && fileType.test(file.type)){ - if(file.size < 204800){ //200 ko - var options = { - maxWidth: 32, - maxHeight: 32, - minWidth: 32, - minHeight: 32 - }; + require([ + "blueimp.loadimage" + ], function (loadImage) { - data.oldImage = $("#img_before").get(0); + $.each(data.files, function (index, file) { + var fileType = /^image\/(gif|jpeg|png)$/; + if(typeof loadImage == 'function' && fileType.test(file.type)){ + if(file.size < 204800){ //200 ko + var options = { + maxWidth: 32, + maxHeight: 32, + minWidth: 32, + minHeight: 32 + }; - loadImage(file, function(img){ - $("#img_before").remove(); - $("#pub_icon .thumb_wrapper").append(img); - $("#pub_icon .thumb_wrapper img").attr("img_before"); - return false; - }, options); + data.oldImage = $("#img_before").get(0); - data.submit(); - } else { - $('#upload-error').empty().append(language.errorFileApiTooBig); + loadImage(file, function(img){ + $("#img_before").remove(); + $("#pub_icon .thumb_wrapper").append(img); + $("#pub_icon .thumb_wrapper img").attr("img_before"); + return false; + }, options); + + data.submit(); + } else { + $('#upload-error').empty().append(language.errorFileApiTooBig); + } } - } + }); }); }, done: function (e, data) { diff --git a/templates/web/admin/statusbit/edit.html.twig b/templates/web/admin/statusbit/edit.html.twig index 7330477824..4001a0626e 100644 --- a/templates/web/admin/statusbit/edit.html.twig +++ b/templates/web/admin/statusbit/edit.html.twig @@ -166,6 +166,7 @@